Task continuation on UI thread
Call the continuation with TaskScheduler.FromCurrentSynchronizationContext()
:
Task UITask= task.ContinueWith(() =>
{
this.TextBlock1.Text = "Complete";
}, TaskScheduler.FromCurrentSynchronizationContext());
This is suitable only if the current execution context is on the UI thread.
Task continuation on UI thread, when started from background thread
You need to get a reference to TaskScheduler.FromCurrentSynchronizationContext() from the UI thread and pass it to the continuation.
Similar to this.
http://reedcopsey.com/2009/11/17/synchronizing-net-4-tasks-with-the-ui-thread/
private void Form1_Load(object sender, EventArgs e)
{
// This requires a label titled "label1" on the form...
// Get the UI thread's context
var context = TaskScheduler.FromCurrentSynchronizationContext();
this.label1.Text = "Starting task...";
// Start a task - this runs on the background thread...
Task task = Task.Factory.StartNew( () =>
{
// Do some fake work...
double j = 100;
Random rand = new Random();
for (int i = 0; i < 10000000; ++i)
{
j *= rand.NextDouble();
}
// It's possible to start a task directly on
// the UI thread, but not common...
var token = Task.Factory.CancellationToken;
Task.Factory.StartNew(() =>
{
this.label1.Text = "Task past first work section...";
}, token, TaskCreationOptions.None, context);
// Do a bit more work
Thread.Sleep(1000);
})
// More commonly, we'll continue a task with a new task on
// the UI thread, since this lets us update when our
// "work" completes.
.ContinueWith(_ => this.label1.Text = "Task Complete!", context);
}
TaskContinuationOptions for completing on the UI Thread
Answering my own question, after comments from @PeterBons.
Additonal code to add to answer at https://stackoverflow.com/a/15120092/858282
/// <summary>
/// as per http://reedcopsey.com/2009/11/17/synchronizing-net-4-tasks-with-the-ui-thread/
/// from UI, store and pass 'TaskScheduler.FromCurrentSynchronizationContext()' into this method to avoid the
/// need for 'Invoke' to avoid cross threading UI exceptions
/// </summary>
public Task<T> QueueTask<T>(Func<T> work, TaskScheduler tScheduler, CancellationToken cancelToken = default(CancellationToken))
{
lock (key)
{
var task = previousTask.ContinueWith(t => work()
, cancelToken == default(CancellationToken) ? CancellationToken.None : cancelToken
, TaskContinuationOptions.None
, tScheduler);
previousTask = task;
return task;
}
}
How to get a Task to return to the UI thread in WPF
You can associate a TaskScheduler
with the continuation task to force the delegate that sets the Content
and Visibility
properties to be set on the UI thread:
var overlayTask = Task.Delay(250, token).ContinueWith(_ => {
Overlay.Content = overlayMessage;
Overlay.Visibility = Visibility.Visible;
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
Task.ContinueWith callback thread
This depends on the scheduler that is associated with the continuation. By default, task continuations are scheduled through the Current
scheduler, being the TaskScheduler
associated with the currently executing task. When ContinueWith
is not called from within a task, Current
will return the Default
scheduler, which is the default TaskScheduler
instance provided by the .NET Framework, and which will schedule your tasks on the thread pool.
If you want to influence this behaviour, you can call one of the ContinueWith
overloads that takes a TaskScheduler
parameter. A common pattern is to pass TaskScheduler.FromCurrentSynchronizationContext()
when creating continuations on the UI thread, as this would cause the continuation to be dispatched back onto the UI thread when executed.
Edit: In reply to your comment: The deadlock may arise if you spawn a child task (intended to run on the thread pool) from a continuation running on the UI thread. In such cases, the child task will inherit the task scheduler from the parent task, which would be bound to the UI thread, causing the child task to run on the UI thread too.
Task.Factory.StartNew(() =>
{
// Do background work.
}).ContinueWith(_ =>
{
// Update UI, then spawn child task to do more background work...
Task.Factory.StartNew(() =>
{
// ...but child task runs on UI thread!
});
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
To resolve this, you can use the StartNew
overload that accepts a TaskScheduler
parameter for the child task, and pass TaskScheduler.Default
to it:
// Update UI, then spawn child task to do more background work...
Task.Factory.StartNew(() =>
{
// ...and child task now runs on the thread pool.
},
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default);
Task continuation blocking UI thread
You're explicitly telling the continuation to run in the current synchronization context by specifying
TaskScheduler.FromCurrentSynchronizationContext()
So yes, that will block the UI thread because it's running in the UI thread assuming this overall method is called in a UI thread. The original task will run in the background, but your continuations are both scheduled to run in the UI thread. If you don't want to do that, don't use that task scheduler.
ContinueWith a Task on the Main thread
Will it wait until the main thread is currently finished doing whatever it is doing, or will it get called immediately after the asynchronous call is complete?
When the asynchronous call completes, the continuation will be scheduled. The effect of that scheduling depends on the scheduler (of course) but for a "normal" WPF or Windows Forms message loop, I'd expect it to be scheduled in a similar way to a call to Control.BeginInvoke
or Dispatcher.BeginInvoke
- in other words, when the "main" thread has finished the rest of the tasks which had been scheduled before this one.
I wouldn't expect the main thread to magically stop what it's doing and execute the continuation. It could make the continuation a high-priority task in its queue, however.
Task ContinueWith() Not Updating Cursor on UI Thread
The cursor change needs to be done on the UI thread. You can either use the the overload of ContinueWith that takes a task scheduler:
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory
.StartNew(() => PerformMigration(legacyTrackerIds))
.ContinueWith(_ => this.Cursor = Cursors.Arrow, uiScheduler);
Or use the Dispatcher.Invoke method:
Task.Factory
.StartNew(() => PerformMigration(legacyTrackerIds))
.ContinueWith(_ => { Dispatcher.Invoke(() => { this.Cursor = Cursors.Arrow; }); });
Task continuation to maintain UI thread responsiveness
I would use the Event Based Asynchronous-TYPE Pattern for this. A simplified version of the code I use to spin-off method onto a background thread using TPL is below
private void TaskSpin(TaskScheduler uiScheduler,
Func<TaskScheduler, object[], bool> asyncMethod,
object[] methodParameters)
{
try
{
Task asyncTask = Task.Factory.StartNew<bool>(() =>
asyncMethod(uiScheduler, methodParameters));
// Callback for finish/cancellation.
asyncTask.ContinueWith(task =>
{
// Check task status.
switch (task.Status)
{
// Handle any exceptions to prevent UnobservedTaskException.
case TaskStatus.RanToCompletion:
if (asyncTask.Result)
UpdateUI(uiScheduler, "OK");
else
{
string strErrComplete = "Process failed.";
UpdateUI(uiScheduler, strErrComplete);
}
break;
case TaskStatus.Faulted:
string strFatalErr = String.Empty;
UpdateUI(uiScheduler, "Fatal Error);
if (task.Exception != null)
strFatalErr = task.Exception.InnerException.Message;
else
strFatalErr = "Operation failed";
MessageBox.Show(strFatalErr);
break;
}
asyncTask.Dispose();
return;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception eX)
{
Utils.ErrMsg(eX.Message);
}
}
I hope this helps.
Edit. Note, in the above uiScheduler
is the TaskScheduler
for the UI Thread. That is
TaskSheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.ContinueWith from calling thread
You can use TaskContinuationOptions.ExecuteSynchronously
:
ContinueWith(() => { ... }, TaskContinuationOptions.ExecuteSynchronously);
ExecuteSynchronously
tells it to try and run the continuation on whatever thread was last executing the antecedent task. And in the case of a continuation executing after a Task.Factory.StartNew
call, that thread would be a thread pool thread.
But you have to note that this is merely a hint and the framework won't always honor your request. So you shouldn't structure your code so that running on the same thread becomes a necessity.
Related Topics
How to Write Fast Colored Output to Console
Custom Numeric Format String to Always Display the Sign
Send Push to Android by C# Using Fcm (Firebase Cloud Messaging)
Programmatically Mouse Click in Another Window
How to Style Chat Window Using CSS When Using Microsoft Bot Framework
How to Add Js and CSS Files in ASP.NET Core
How to Extract Data from a Datatable
Cross Platform (PHP to C# .Net) Encryption/Decryption with Rijndael
How to Return Text from Native (C++) Code
How to Get Memcached Running on a Windows (X64) 64Bit Environment
Jit Compiler VS Offline Compilers
How to Call Code Behind Server Method from a Client Side JavaScript Function
JavaScript Date to C# via Ajax
Return HTML from ASP.NET Web API
How to Pass Constructor Parameters to Unity's Resolve() Method