Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

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?

Edit

Also tried Task.Wait, but the waiting never ends:

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(); } 
like image 384
McGarnagle Avatar asked Nov 30 '13 18:11

McGarnagle


People also ask

Do async functions run on another thread?

No, it does not. It MAY start another thread internally and return that task, but the general idea is that it does not run on any thread.

Does async run on main thread?

init , the asynchronous code runs on the current actor, which, in this context is the main actor. This has the result of blocking the main thread. By calling Task.

Is task run background thread?

You can start running a Task using Task. Run(Action action) . This will queue up the Task on the thread pool, which will run in the background on a different thread. The thread pool takes a queue of tasks, and assigns them to CPU threads for processing.

How do you call async method without await?

However, just to address "Call an async method in C# without await", you can execute the async method inside a Task. Run . This approach will wait until MyAsyncMethod finish. await asynchronously unwraps the Result of your task, whereas just using Result would block until the task had completed.


2 Answers

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:

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

Task.Run will start something in a thread pool thread. When you await something, it automatically comes back in on the execution context which started it. In this case, your UI thread.

You should generally never need to call Start(). Prefer async methods, Task.Run, and Task.Factory.StartNew -- all of which start the tasks automatically. Continuations created with await or ContinueWith are also started automatically when their parent completes.

like image 116
Cory Nelson Avatar answered Sep 28 '22 03:09

Cory Nelson


WARNING about using FromCurrentSynchronizationContext:

Ok, Cory knows how to make me rewrite answer:).

So the main culprit is actually the FromCurrentSynchronizationContext! Any time StartNew or ContinueWith runs on this kind scheduler, it runs on the UI Thread. One may think:

OK, let's start subsequent operations on UI, change some controls, spawn some operations. 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. UI aps are usually complex, unease to maintain certainty, that nothing will call another StartNew operation, simple example here:

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! - OK
  2. TestA.Begin:ThreadUI! - OK
  3. TestA.End:ThreadUI! - OK
  4. Form1:ThreadUI! - OK
  5. TestA.StartNew:ThreadPool - OK
  6. TestA.ContinuWith:ThreadUI! - OK
  7. TestB.Begin:ThreadUI! - OK
  8. TestB.End:ThreadUI! - OK
  9. TestB.StartNew - expected ThreadPool:ThreadUI! - COULD BE UNEXPECTED!
  10. TestB.ContinueWith1 should be ThreadPool:ThreadUI! - COULD BE UNEXPECTED!
  11. TestB.ContinueWith2:ThreadUI! - OK

Please notice, that tasks returned by:

  1. async method,
  2. Task.Fatory.StartNew,
  3. Task.Run,

can not be started! They are already hot tasks...

like image 35
ipavlu Avatar answered Sep 28 '22 04:09

ipavlu