Task continuation on UI thread - c#

Is there a 'standard' way to specify that a task continuation should run on the thread from which the initial task was created?
Currently I have the code below - it is working but keeping track of the dispatcher and creating a second Action seems like unnecessary overhead.
dispatcher = Dispatcher.CurrentDispatcher;
Task task = Task.Factory.StartNew(() =>
{
DoLongRunningWork();
});
Task UITask= task.ContinueWith(() =>
{
dispatcher.Invoke(new Action(() =>
{
this.TextBlock1.Text = "Complete";
}
});

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.

With async you just do:
await Task.Run(() => do some stuff);
// continue doing stuff on the same context as before.
// while it is the default it is nice to be explicit about it with:
await Task.Run(() => do some stuff).ConfigureAwait(true);
However:
await Task.Run(() => do some stuff).ConfigureAwait(false);
// continue doing stuff on the same thread as the task finished on.

If you have a return value you need to send to the UI you can use the generic version like this:
This is being called from an MVVM ViewModel in my case.
var updateManifest = Task<ShippingManifest>.Run(() =>
{
Thread.Sleep(5000); // prove it's really working!
// GenerateManifest calls service and returns 'ShippingManifest' object
return GenerateManifest();
})
.ContinueWith(manifest =>
{
// MVVM property
this.ShippingManifest = manifest.Result;
// or if you are not using MVVM...
// txtShippingManifest.Text = manifest.Result.ToString();
System.Diagnostics.Debug.WriteLine("UI manifest updated - " + DateTime.Now);
}, TaskScheduler.FromCurrentSynchronizationContext());

I just wanted to add this version because this is such a useful thread and I think this is a very simple implementation. I have used this multiple times in various types if multithreaded application:
Task.Factory.StartNew(() =>
{
DoLongRunningWork();
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{ txt.Text = "Complete"; }));
});

Got here through google because i was looking for a good way to do things on the ui thread after being inside a Task.Run call - Using the following code you can use await to get back to the UI Thread again.
I hope this helps someone.
public static class UI
{
public static DispatcherAwaiter Thread => new DispatcherAwaiter();
}
public struct DispatcherAwaiter : INotifyCompletion
{
public bool IsCompleted => Application.Current.Dispatcher.CheckAccess();
public void OnCompleted(Action continuation) => Application.Current.Dispatcher.Invoke(continuation);
public void GetResult() { }
public DispatcherAwaiter GetAwaiter()
{
return this;
}
}
Usage:
... code which is executed on the background thread...
await UI.Thread;
... code which will be run in the application dispatcher (ui thread) ...

Related

How to call Dispatcher.RunAsync() in a thread safe method to update UI components [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
First, I don’t think I have titled this correctly – after much thought.
However, I have been trying to find a way to update a UI control from multiple threads spawned from a Task.Run(async ()=>{ }) expression/method. I’m doing this in an UWP windows mobile 10 application.
I've listed majority of the routines involved, and need help with completing the call to the DisplayProgress(string message) method. In this method, I want to update the UI control in a thread safe manner using Dispathcer.RunAsync(), but need it to be thread safe. Please see Attempt 1&2 in the code below.
public sealed partial class SynchProcess : BasePage
{
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Task.Run(async ()=>{
var result = await SynchTables();
});
}
private async Task<bool> SynchTables()
{
Bool bRet = true;
List<Task> tasks = new List<Task>();
Try
{
// Refresh 1
tasks.Add(Task.Run(async () =>
{
// Update UI
DisplayProgress(“Cars refresh…running”);
List<Car> cars = await _dataService.GetCarsData();
DataAccessSQLite.DeleteAll<Car>();
DataAccessSQLite.InsertCars(cars);
// Update UI
DisplayProgress(“Cars refresh…completed”);
}));
// Refresh 2
tasks.Add(Task.Run(async () =>
{
//Update UI
DisplayProgress(“Tracks refresh…running”);
List<Track> tracks = await _dataService.GeTrackstData();
DataAccessSQLite.DeleteAll<Track>();
DataAccessSQLite.InsertTracks(tracks);
//Update UI
DisplayProgress(“Tracks refresh…completed”);
}));
// Refresh 3
// Refresh 4
…
…
Task.WaitAll(tasks.ToArray());
}
Catch(AggreggateException agEx)
{
bRet = false;
// removed for brevity
…
}
return bRet;
}
// Attempt 1 : This i was aware of, but included for completeness.
// FAIL - The await operator can only be used in an async method.
private static void DisplayProgress(string message)
{
lock(_lockObject)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
//Update control logic;
});
}
// Attempt 2
// FAIL - An object reference is required for non-static field, method or
// property ‘DependencyObject.Dispatcher’
private static async Task DisplayProgress (string message)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
// Update UI logic here
});
}
}
I found this article http://briandunnington.github.io/uitask.html, however, I’m not sure if this approach is correct or thread safe. Seems like a lot of boilerplate code for something that should be handled by Dispatacher.RunAsync()? Additional articles I’ve read pointed to Action delegates, which I think is supposed to be thread safe, but after several (very detailed technical articles) I am now confused. Additional articles: Are C# delegates thread-safe? (unfortunately, I can only link to 2 articles - need to increase my points first !)
I’m not sure if this approach is correct or thread safe. Seems like a lot of boilerplate code for something that should be handled by Dispatacher.RunAsync()?
You can access the UI element safely by using its Dispatcher property, or the Dispatcher property of any object that exists in the context of the UI thread (such as the page the button is on).
The lifetime of a Windows Runtime object that is created on the UI thread is bounded by the lifetime of the thread. Do not try to access objects on a UI thread after the window has closed.
The following is official suggestion on a Microsoft github repository.
Normally, you can update your UI from a background thread by calling Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => /* update the UI */). However, this merely schedules the work on the UI thread and returns immediately, even if the task is to await user input in a popup box. It also does not provide a way for the task to return a result to the caller.
RunTaskAsync provides an alternative that uses TaskCompletionSource in combination with RunAsync to return a Task that you can await from your background thread, thereby pausing execution until the UI task completes.
Because RunTaskAsync is an extension method, you call it as if it were a method on Dispatcher: var result = await Dispatcher.RunTaskAsync(async () => { ...; return value; });
using System;
using System.Threading.Tasks;
using Windows.UI.Core;
public static class DispatcherTaskExtensions
{
public static async Task<T> RunTaskAsync<T>(this CoreDispatcher dispatcher,
Func<Task<T>> func, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
var taskCompletionSource = new TaskCompletionSource<T>();
await dispatcher.RunAsync(priority, async () =>
{
try
{
taskCompletionSource.SetResult(await func());
}
catch (Exception ex)
{
taskCompletionSource.SetException(ex);
}
});
return await taskCompletionSource.Task;
}
// There is no TaskCompletionSource<void> so we use a bool that we throw away.
public static async Task RunTaskAsync(this CoreDispatcher dispatcher,
Func<Task> func, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) =>
await RunTaskAsync(dispatcher, async () => { await func(); return false; }, priority);
}
Is it necessary to lock code snippet where multiple threads access same uwp component via dispatcher?
You don't have to add a lock. Dispatcher.RunTaskAsync requests will not run in the middle of other code (that's the whole point of them).
This solved the issue:
private async Task DisplayProgess(string message)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
lsvInvSynchProgress.Items.Add($"{message} - {DateTime.Now}");
});
}
The revised code
public sealed partial class SynchProcess : BasePage
{
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Task.Run(async ()=>{
var result = await SynchTables();
});
}
private async Task<bool> SynchTables()
{
Bool bRet = true;
List<Task> tasks = new List<Task>();
Try
{
// Refresh 1
tasks.Add(Task.Run(async () =>
{
// Update UI
await DisplayProgress(“Cars refresh…running”);
List<Car> cars = await _dataService.GetCarsData();
DataAccessSQLite.DeleteAll<Car>();
DataAccessSQLite.InsertCars(cars);
// Update UI
await DisplayProgress(“Cars refresh…completed”);
}));
// Refresh 2
// Refresh 3
// Refresh 4
…
…
Task.WaitAll(tasks.ToArray());
}
Catch(AggreggateException agEx)
{
bRet = false;
}
return bRet;
}
private async Task DisplayProgess(string message)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
lsvInvSynchProgress.Items.Add($"{message} - {DateTime.Now}");
});
}
}

NUnit test with Application loop in it hangs when Form is created before it

I have a few tests with WebBrowser control wrapped with MessageLoopWorker as described here: WebBrowser Control in a new thread
But when another test creates user control or form, the test freezes and never completes:
[Test]
public async Task WorksFine()
{
await MessageLoopWorker.Run(async () => new {});
}
[Test]
public async Task NeverCompletes()
{
using (new Form()) ;
await MessageLoopWorker.Run(async () => new {});
}
// a helper class to start the message loop and execute an asynchronous task
public static class MessageLoopWorker
{
public static async Task<object> Run(Func<object[], Task<object>> worker, params object[] args)
{
var tcs = new TaskCompletionSource<object>();
var thread = new Thread(() =>
{
EventHandler idleHandler = null;
idleHandler = async (s, e) =>
{
// handle Application.Idle just once
Application.Idle -= idleHandler;
// return to the message loop
await Task.Yield();
// and continue asynchronously
// propogate the result or exception
try
{
var result = await worker(args);
tcs.SetResult(result);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
// signal to exit the message loop
// Application.Run will exit at this point
Application.ExitThread();
};
// handle Application.Idle just once
// to make sure we're inside the message loop
// and SynchronizationContext has been correctly installed
Application.Idle += idleHandler;
Application.Run();
});
// set STA model for the new thread
thread.SetApartmentState(ApartmentState.STA);
// start the thread and await for the task
thread.Start();
try
{
return await tcs.Task;
}
finally
{
thread.Join();
}
}
}
The code steps-in well except return await tcs.Task; never returns.
Wrapping new Form into the MessageLoopWorker.Run(...) seems to make it better, but it does not work with more complicated code, unfortunately. And I have quite a lot of other tests with forms and user controls that I would like to avoid wrapping into messageloopworker.
Maybe MessageLoopWorker can be fixed to avoid the interference with other tests?
Update: following the amazing answer by #Noseratio I've reset the synchronisation context before the MessageLoopWorker.Run call and it now works well.
More meaningful code:
[Test]
public async Task BasicControlTests()
{
var form = new CustomForm();
form.Method1();
Assert....
}
[Test]
public async Task BasicControlTests()
{
var form = new CustomForm();
form.Method1();
Assert....
}
[Test]
public async Task WebBrowserExtensionTest()
{
SynchronizationContext.SetSynchronizationContext(null);
await MessageLoopWorker.Run(async () => {
var browser = new WebBrowser();
// subscribe on browser's events
// do something with browser
// assert the event order
});
}
When running the tests without nulling the sync context WebBrowserExtensionTest blocks when it follows BasicControlTests. With nulling it pass well.
Is it ok to keep it like this?
I repro'ed this under MSTest, but I believe all of the below applies to NUnit equally well.
First off all, I understand this code might have been taken out of context, but as is, it doesn't seem to be very useful. Why would you want to create a form inside NeverCompletes, which runs on an random MSTest/NUnit thread, different from the thread spawned by MessageLoopWorker?
Anyhow, you're having a deadlock because using (new Form()) installs an instance of WindowsFormsSynchronizationContext on that original unit test thread. Check SynchronizationContext.Current after the using statement. Then, you facing a classic deadlock well explained by Stephen Cleary in his "Don't Block on Async Code".
Right, you don't block yourself but MSTest/NUnit does, because it is smart enough to recognize async Task signature of NeverCompletes method and then execute something like Task.Wait on the Task returned by it. Because the original unit test thread doesn't have a message loop and doesn't pump messages (unlike is expected by WindowsFormsSynchronizationContext), the await continuation inside NeverCompletes never gets a chance to execute and Task.Wait is just hanging waiting.
That said, MessageLoopWorker was only designed to create and run WinForms object inside the scope of the async method you pass to MessageLoopWorker.Run, and then be done. E.g., the following wouldn't block:
[TestMethod]
public async Task NeverCompletes()
{
await MessageLoopWorker.Run(async (args) =>
{
using (new Form()) ;
return Type.Missing;
});
}
It was not designed to work with WinForms objects across multiple MessageLoopWorker.Run calls. If that's what you need, you may want to look at my MessageLoopApartment from here, e.g.:
[TestMethod]
public async Task NeverCompletes()
{
using (var apartment = new MessageLoopApartment())
{
// create a form inside MessageLoopApartment
var form = apartment.Invoke(() => new Form {
Width = 400, Height = 300, Left = 10, Top = 10, Visible = true });
try
{
// await outside MessageLoopApartment's thread
await Task.Delay(2000);
await apartment.Run(async () =>
{
// this runs on MessageLoopApartment's STA thread
// which stays the same for the life time of
// this MessageLoopApartment instance
form.Show();
await Task.Delay(1000);
form.BackColor = System.Drawing.Color.Green;
await Task.Delay(2000);
form.BackColor = System.Drawing.Color.Red;
await Task.Delay(3000);
}, CancellationToken.None);
}
finally
{
// dispose of WebBrowser inside MessageLoopApartment
apartment.Invoke(() => form.Dispose());
}
}
}
Or, you can even use it across multiple unit test methods, if you're not concerned about potential coupling of tests, e.g. (MSTest):
[TestClass]
public class MyTestClass
{
static MessageLoopApartment s_apartment;
[ClassInitialize]
public static void TestClassSetup()
{
s_apartment = new MessageLoopApartment();
}
[ClassCleanup]
public void TestClassCleanup()
{
s_apartment.Dispose();
}
// ...
}
Finally, neither MessageLoopWorker nor MessageLoopApartment were designed to work with WinForms object created on different threads (which is almost never a good idea anyway). You can have as many MessageLoopWorker/MessageLoopApartment instances as you like, but once a WinForm object has been created on the thread of a particular MessageLoopWorker/MessageLoopApartment instance, it should further be accessed and properly destroyed on the same thread only.

Creating and starting a task on the UI thread

When a method that gets called on a worker thread needs to run code on the UI thread and wait for it to complete before doing something else, it can be done like this:
public int RunOnUi(Func<int> f)
{
int res = Application.Current.Dispatcher.Invoke(f);
return res;
}
But what if I wanted to do it with tasks? Is there a way for the RunOnUi method to create a task that is started on the UI and return it so that the caller (which runs on a worker thread) can wait for it? Something that will fit the following signature: public Task<int> StartOnUi(Func<int> f) ?
One way to do it is as follows:
public Task<int> RunOnUi(Func<int> f)
{
var task = new Task<int>(f);
task.Start(_scheduler);
return task;
}
Here, assume that _schduler holds the ui TaskScheduler. But I am not too comfortable with creating "cold" tasks and using the start method to run them. Is that the "recommended" way or is there a more elegant way to do it?
Just use InvokeAsync instead of Invoke then return the Task<int> inside the DispatcherOperation<int> the function returns.
//Coding conventions say async functions should end with the word Async.
public Task<int> RunOnUiAsync(Func<int> f)
{
var dispatcherOperation = Application.Current.Dispatcher.InvokeAsync(f);
return dispatcherOperation.Task;
}
If you do not have access to .NET 4.5 it is a little more complicated. You will need to use BeginInvoke and a TaskCompletionSource to wrap the DispaterOperation that BeginInvoke returns
public Task<int> RunOnUi(Func<int> f)
{
var operation = Application.Current.Dispatcher.BeginInvoke(f);
var tcs = new TaskCompletionSource<int>();
operation.Aborted += (sender, args) => tcs.TrySetException(new SomeExecptionHere());
operation.Completed += (sender, args) => tcs.TrySetResult((int)operation.Result);
//The operation may have already finished and this check accounts for
//the race condition where neither of the events will ever be called
//because the events where raised before you subscribed.
var status = operation.Status;
if (status == DispatcherOperationStatus.Completed)
{
tcs.TrySetResult((int)operation.Result);
}
else if (status == DispatcherOperationStatus.Aborted)
{
tcs.TrySetException(new SomeExecptionHere());
}
return tcs.Task;
}

How do I continue after multiple Tasks without blocking the UI thread?

In my MVVM application my view model calls 3 different service methods, converts the data from each into a common format and then updates the UI using property notification/observable collections etc.
Each method in the service layer starts a new Task and returns the Task to the view model. Here's an example of one of my service methods.
public class ResourceService
{
internal static Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<Exception> errorCallback)
{
var t = Task.Factory.StartNew(() =>
{
//... get resources from somewhere
return resources;
});
t.ContinueWith(task =>
{
if (task.IsFaulted)
{
errorCallback(task.Exception);
return;
}
completedCallback(task.Result);
}, TaskScheduler.FromCurrentSynchronizationContext());
return t;
}
}
Here's the calling code and other relevant parts of the view model...
private ObservableCollection<DataItem> Data = new ObservableCollection<DataItem>();
public ICollectionView DataView
{
get { return _dataView; }
set
{
if (_dataView != value)
{
_dataView = value;
RaisePropertyChange(() => DataView);
}
}
}
private void LoadData()
{
SetBusy("Loading...");
Data.Clear();
Task[] tasks = new Task[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};
Task.WaitAll(tasks);
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterTimelineData;
IsBusy = false;
}
private Task LoadResources()
{
return ResourceService.LoadResources(resources =>
{
foreach(var r in resources)
{
var d = convertResource(r);
Data.Add(d);
}
},
error =>
{
// do some error handling
});
}
This almost works but there are a couple of small issues.
Number 1: In the call to SetBusy at the very beginning, before I start any tasks and before I call WaitAll, I set the IsBusy property to true. This should update the UI and show the BusyIndicator control but it's not working. I've also tried adding simple string properties and binding those and they're not being updated either. The IsBusy functionality is part of a base class and works in other view models where I don't have more than one Task running so I don't believe there is an issue with the property notification or data binding in the XAML.
All the data bindings seem to be updated after the whole method has completed. I'm not seeing any "first time exceptions" or binding errors in the output Window which is leading me to believe the UI thread is somehow being blocked before the call to WaitAll.
Number 2: I seem to be returning the wrong Tasks from the service methods. I want everything after WaitAll to run after the view model has converted all the results from all the service methods in the callbacks. However if I return the continuation task from the service method the continuation never gets called and WaitAll waits forever. The strange thing is the UI control bound to the ICollectionView actually displays everything correctly, I assumed this is because Data is an observable collection and the CollectionViewSource is aware of the collection changed events.
You can use TaskFactory.ContinueWhenAll to build a continuation that runs when the input tasks all complete.
Task[] tasks = new Task[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};
Task.Factory.ContinueWhenAll(tasks, t =>
{
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterTimelineData;
IsBusy = false;
}, CancellationToken.None, TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
Note that this becomes simpler if you use C# 5's await/async syntax:
private async void LoadData()
{
SetBusy("Loading...");
Data.Clear();
Task[] tasks = new Task[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};
await Task.WhenAll(tasks);
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterTimelineData;
IsBusy = false;
}
However if I return the continuation task from the service method the continuation never gets called and WaitAll waits forever
The problem is that your continuation task requires the UI thread, and you're blocking the UI thread in the WaitAll call. This creates a deadlock which will not resolve.
Fixing the above should correct this - you'll want to return the Continuation as the Task, as that's what you need to wait for completion - but by using TaskFactory.ContinueWhenAll you free up the UI thread so it can process those continuations.
Note that this is another thing that gets simplified with C# 5. You can write your other methods as:
internal static async Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<Exception> errorCallback)
{
try
{
await Task.Run(() =>
{
//... get resources from somewhere
return resources;
});
}
catch (Exception e)
{
errorCallback(task.Exception);
}
completedCallback(task.Result);
}
That being said, it's typically better to write the methods to return a Task<T> instead of providing callbacks, as that simplifies both ends of the usage.

How to create a task (TPL) running a STA thread?

Using Thread is pretty straightforward
Thread thread = new Thread(MethodWhichRequiresSTA);
thread.SetApartmentState(ApartmentState.STA);
How to accomplish the same using Tasks in a WPF application? Here is some code:
Task.Factory.StartNew
(
() =>
{return "some Text";}
)
.ContinueWith(r => AddControlsToGrid(r.Result));
I'm getting an InvalidOperationException with
The calling thread must be STA, because many UI components require this.
You can use the TaskScheduler.FromCurrentSynchronizationContext Method to get a TaskScheduler for the current synchronization context (which is the WPF dispatcher when you're running a WPF application).
Then use the ContinueWith overload that accepts a TaskScheduler:
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(...)
.ContinueWith(r => AddControlsToGrid(r.Result), scheduler);
Dispatcher.Invoke could be a solution. e.g.
private async Task<bool> MyActionAsync()
{
// await for something, then return true or false
}
private void StaContinuation(Task<bool> t)
{
myCheckBox.IsChecked = t.Result;
}
private void MyCaller()
{
MyActionAsync().ContinueWith((t) => Dispatcher.Invoke(() => StaContinuation(t)));
}

Categories

Resources