在后台线程上运行“async”方法

时间:2013-11-30 18:51:03

标签: c# windows-phone windows-phone-7.1 async-await

我正在尝试从普通方法运行“异步”方法:

public string Prop
{
    get { return _prop; }
    set
    {
        _prop = value;
        RaisePropertyChanged();
    }
}

private async Task<string> GetSomething()
{
    return await new Task<string>( () => {
        Thread.Sleep(2000);
        return "hello world";
    });
}

public void Activate()
{
    GetSomething.ContinueWith(task => Prop = task.Result).Start();
    // ^ exception here
}

抛出的异常是:

  

启动可能不会在继续任务上调用。

这意味着什么呢?如何在后台线程上运行异步方法,将结果发送回UI线程?

修改

还尝试Task.Wait,但等待永远不会结束:

public void Activate()
{
    Task.Factory.StartNew<string>( () => {
        var task = GetSomething();
        task.Wait();

        // ^ stuck here

        return task.Result;
    }).ContinueWith(task => {
        Prop = task.Result;
    }, TaskScheduler.FromCurrentSynchronizationContext());
    GetSomething.ContinueWith(task => Prop = task.Result).Start();
}

2 个答案:

答案 0 :(得分:35)

专门修复您的示例:

public void Activate()
{
    Task.Factory.StartNew(() =>
    {
        //executes in thread pool.
        return GetSomething(); // returns a Task.
    }) // returns a Task<Task>.
    .Unwrap() // "unwraps" the outer task, returning a proxy
              // for the inner one returned by GetSomething().
    .ContinueWith(task =>
    {
        // executes in UI thread.
        Prop = task.Result;
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

这样可行,但这是老派。

在后台线程上运行某些东西并发送回UI线程的现代方法是使用Task.Run()asyncawait

async void Activate()
{
    Prop = await Task.Run(() => GetSomething());
}

Task.Run将在线程池线程中启动。当你await时,它会自动返回启动它的执行上下文。在这种情况下,您的UI线程。

您通常不需要致电Start()。首选async种方法,Task.RunTask.Factory.StartNew - 所有方法都会自动启动任务。使用awaitContinueWith创建的续订也会在其父项完成时自动启动。

答案 1 :(得分:1)

关于使用FromCurrentSynchronizationContext的警告:

好的,Cory知道如何让我重写答案:)。

所以主要的罪魁祸首实际上是FromCurrentSynchronizationContext! 只要StartNew或ContinueWith在这种调度程序上运行,它就会在UI线程上运行。有人可能会想:

好的,让我们在UI上开始后续操作,更改一些控件,生成一些操作。但是从现在开始TaskScheduler.Current不为null,如果任何控件有一些事件,那么会产生一些期望在ThreadPool上运行的StartNew,然后从那里出错。 UI aps通常很复杂,不耐烦以保持确定性,没有任何东西可以调用另一个StartNew操作,这里有一个简单的例子:

public partial class Form1 : Form
{
    public static int Counter;
    public static int Cnt => Interlocked.Increment(ref Counter);
    private readonly TextBox _txt = new TextBox();
    public static void WriteTrace(string from) => Trace.WriteLine($"{Cnt}:{from}:{Thread.CurrentThread.Name ?? "ThreadPool"}");

    public Form1()
    {
        InitializeComponent();
        Thread.CurrentThread.Name = "ThreadUI!";

        //this seems to be so nice :)
        _txt.TextChanged += (sender, args) => { TestB(); };

        WriteTrace("Form1"); TestA(); WriteTrace("Form1");
    }
    private void TestA()
    {
        WriteTrace("TestA.Begin");
        Task.Factory.StartNew(() => WriteTrace("TestA.StartNew"))
        .ContinueWith(t =>
        {
            WriteTrace("TestA.ContinuWith");
            _txt.Text = @"TestA has completed!";
        }, TaskScheduler.FromCurrentSynchronizationContext());
        WriteTrace("TestA.End");
    }
    private void TestB()
    {
        WriteTrace("TestB.Begin");
        Task.Factory.StartNew(() => WriteTrace("TestB.StartNew - expected ThreadPool"))
        .ContinueWith(t => WriteTrace("TestB.ContinueWith1 should be ThreadPool"))
        .ContinueWith(t => WriteTrace("TestB.ContinueWith2"));
        WriteTrace("TestB.End");
    }
}
  1. Form1中:ThreadUI! - 好的
  2. TestA.Begin:ThreadUI! - 好的
  3. TestA.End:ThreadUI! - 好的
  4. Form1中:ThreadUI! - 好的
  5. TestA.StartNew:ThreadPool - 确定
  6. TestA.ContinuWith:ThreadUI! - 好的
  7. TestB.Begin:ThreadUI! - 好的
  8. TestB.End:ThreadUI! - 好的
  9. TestB.StartNew - 期望的ThreadPool:ThreadUI! - 可能会意外!
  10. TestB.ContinueWith1应该是ThreadPool:ThreadUI! - 可能会意外!
  11. TestB.ContinueWith2:ThreadUI! - 好的
  12. 请注意,任务返回:

    1. 异步方法,
    2. Task.Fatory.StartNew,
    3. Task.Run,​​
    4. 无法启动!他们已经是热门的任务......