简体   繁体   English

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

[英]Run “async” method on a background thread

I'm trying to run an "async" method from an ordinary method: 我正在尝试从普通方法运行“异步”方法:

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
}

The exception thrown is: 抛出的异常是:

Start may not be called on a continuation task. 可能不会在继续任务上调用Start。

What does that mean, anyway? 无论如何,这是什么意思? How can I simply run my async method on a background thread, dispatch the result back to the UI thread? 如何在后台线程上运行异步方法,将结果发送回UI线程?

Edit 编辑

Also tried Task.Wait , but the waiting never ends: 还试过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();
}

To fix your example specifically: 要具体修复您的示例:

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());
}

This will work, but it's old-school. 这可行,但它是老派。

The modern way to run something on a background thread and dispatch back to UI thread is to use Task.Run() , async , and await : 在后台线程上运行某些东西并调度回UI线程的现代方法是使用Task.Run()asyncawait

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

Task.Run will start something in a thread pool thread. Task.Run将在线程池线程中启动一些东西。 When you await something, it automatically comes back in on the execution context which started it. await某事时,它会自动返回启动它的执行上下文。 In this case, your UI thread. 在这种情况下,您的UI线程。

You should generally never need to call Start() . 您通常不需要调用Start() Prefer async methods, Task.Run , and Task.Factory.StartNew -- all of which start the tasks automatically. 首选async方法, Task.RunTask.Factory.StartNew - 所有这些方法Task.Run自动启动任务。 Continuations created with await or ContinueWith are also started automatically when their parent completes. 使用awaitContinueWith创建的Continuations也会在其父项完成时自动启动。

WARNING about using FromCurrentSynchronizationContext: 关于使用FromCurrentSynchronizationContext的警告:

Ok, Cory knows how to make me rewrite answer:). 好吧,Cory知道如何让我重写答案:)。

So the main culprit is actually the FromCurrentSynchronizationContext! 所以主要的罪魁祸首是FromCurrentSynchronizationContext! Any time StartNew or ContinueWith runs on this kind scheduler, it runs on the UI Thread. 只要StartNew或ContinueWith在这种调度程序上运行,它就会在UI线程上运行。 One may think: 有人可能会想:

OK, let's start subsequent operations on UI, change some controls, spawn some operations. 好的,让我们在UI上开始后续操作,更改一些控件,生成一些操作。 But from now TaskScheduler.Current is not null and if any control has some events, that spawn some StartNew expecting to be running on ThreadPool, then from there it goes wrong. 但是从现在开始TaskScheduler.Current不为null,如果任何控件有一些事件,那么会产生一些期望在ThreadPool上运行的StartNew,然后从那里出错。 UI aps are usually complex, unease to maintain certainty, that nothing will call another StartNew operation, simple example here: 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! Form1中:ThreadUI! - OK - 好
  2. TestA.Begin:ThreadUI! TestA.Begin:ThreadUI! - OK - 好
  3. TestA.End:ThreadUI! TestA.End:ThreadUI! - OK - 好
  4. Form1:ThreadUI! Form1中:ThreadUI! - OK - 好
  5. TestA.StartNew:ThreadPool - OK TestA.StartNew:ThreadPool - 好的
  6. TestA.ContinuWith:ThreadUI! TestA.ContinuWith:ThreadUI! - OK - 好
  7. TestB.Begin:ThreadUI! TestB.Begin:ThreadUI! - OK - 好
  8. TestB.End:ThreadUI! TestB.End:ThreadUI! - OK - 好
  9. TestB.StartNew - expected ThreadPool:ThreadUI! TestB.StartNew - 期望的ThreadPool:ThreadUI! - COULD BE UNEXPECTED! - 可能会意外!
  10. TestB.ContinueWith1 should be ThreadPool:ThreadUI! TestB.ContinueWith1应该是ThreadPool:ThreadUI! - COULD BE UNEXPECTED! - 可能会意外!
  11. TestB.ContinueWith2:ThreadUI! TestB.ContinueWith2:ThreadUI! - OK - 好

Please notice, that tasks returned by: 请注意,返回的任务由:

  1. async method, 异步方法,
  2. Task.Fatory.StartNew, Task.Fatory.StartNew,
  3. Task.Run, Task.Run,

can not be started! 无法启动! They are already hot tasks... 他们已经是热门任务......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM