Let's say I have a Form that tries to deal with a multi-threaded environment; it therefore checks if it's running on the UI thread before any UI modification is done:
partial class SomeForm : Form
{
public void DoSomethingToUserInterface()
{
if (InvokeRequired)
{
BeginInvoke(delegate { DoSomethingToUserInterface() });
}
else
{
… // do the actual work (e.g. manipulate the form or its elements)
}
}
}
Now let's say I am performing some lengthy operation inside the … part of that method; I'd therefore like to make it asynchronous using async/await.
Given that I should change the method signature to return a Task instead of void (so that exceptions can be caught), how would I implement the part that performs the BeginInvoke? What should it return?
public async Task DoSomethingToUserInterfaceAsync()
{
if (InvokeRequired)
{
// what do I put here?
}
{
… // like before (but can now use `await` expressions)
}
}
When using async-await with a custom awaiter such as the common TaskAwaiter, the SynchronizationContext is being implicitly captured for you. Later, when the asynchronous method completes, the continuation (any code after the await) is being marshaled back to that same sync context using SynchronizationContext.Post.
This altogether eliminates the need to use InvokeRequired and other techniques used to mainpulate work on the UI thread. In order to do that, you'll have to trace your method calls all the way to the top level ones and refactor them to use async-await probably.
But, to address the specific problem as is, what you can do is capture the WinFormSynchronizationContext when your Form initializes:
partial class SomeForm : Form
{
private TaskScheduler _uiTaskScheduler;
public SomeForm()
{
_uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
}
And later use it when you want to await:
if (InvokeRequired)
{
Task uiTask = new Task(() => DoSomethingToUserInterface());
uiTask.RunSynchronously(_uiTaskScheduler);
}
else
{
// Do async work
}
You can use TaskScheduler.FromCurrentSynchronizationContext to get the task scheduler for current synchronization context(For UI thread), and store it in a field for later use.
Then when you're interested to start any task in UI thread, you have to pass the uiScheduler to the StartNew method, so that TPL will schedule the task in the provided scheduler(UI thread in this case).
Anyway you decided to run the stuff in UI thread, so just schedule it to UIScheduler, you don't need to check for InvokeRequired whatsoever.
public async Task DoSomethingToUserInterfaceAsync()
{
await Task.Factory.StartNew(() => DoSomethingToUserInterface(), CancellationToken.None, TaskCreationOptions.None, uiScheduler);
...
}
To retrieve the UI scheduler, you can use the following code
private TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Note: It is extremely important that TaskScheduler.FromCurrentSynchronizationContext should be called only from UI thread, otherwise, it will throw exception, or you'll get a TaskScheduler for some other SynchronizationContext which won't do what you need.
Also note that if you have started the async operation from UI thread itself, you don't need any of the above magic. await will resume in the context where it has been started.
Related
I am working on an old WinForms application. I'm trying to make a piece of code run asynchronously, so it doesn't block the UI thread. I'm having some difficulties applying the pattern. Here's a simplified version of my code (before modifications):
void InitializeMyGui()
{
//some code
if(someObject.SomeConditionIsMet())
{
//some code
}
else
{
//some other code
}
// some more code
}
This method is called from two places. One is the constructor:
public MyGui()
{
// some code
InitializeMyGui();
// some more code
}
The other is a method that is triggered by an event handler (this application is older than async/await, event driven programming was used to achieve asynchronous execution)
private void OnSomeOtherStuffFinishedLoading(object sender, EventArgs e)
{
// some code
InitializeMyGui();
// some more code
}
Problem is SomeConditionIsMet() contains a database call, so it needs to be asynchronous to avoid long operations blocking the UI thread if the DB call is slow. No worries, I've modified my methods thusly:
public async Task<bool> SomeConditionIsMet() {
//some code
await dbAdapter.ExecuteAsync();
//process and return result
}
Now InitializeMyGui() obviously needs to await SomeConditionIsMet():
async void InitializeMyGui()
{
//some code
bool conditionMet = await someObject.SomeConditionIsMet();
if(conditionMet)
{
//some code
}
else
{
//some other code
}
// some more code
}
And herein lies the problem. InitializeMyGui() is now async, and should be awaited. Indeed, everything I've read regarding async/await explicitly states you should await async calls all the way to the top. But while I could probably make OnSomeOtherStuffFinishedLoading() async and have it await InitializeMyGui(), I can't make the constructor async.
In this case, it's not such a big deal. InitializeMyGui() is a void, it's not returning any value. But if I understand it correctly, if I don't await the method, the execution will continue before it returns, and the code that comes after, (in my pseudocode marked // some more code) could - and in all likelihood will - execute before InitializeMyGui() finishes.
What I'm missing is a way to say "Hey, run this async method, and then continue with this code here, but do it all asynchronously so that you're not blocking the UI thread" but without making the calling mehtod itself async. Is there a way to do this?
EDIT:
I came up with this:
Task.Run(InitializeMyGui()).ContinueWith(t => {// some other code});
Will this work, or am I somehow violating the sanctity of the UI thread?
First off, InitializeMyGui should be async Task, not async void.
To solve your problem, you need to accept that you now have an additional state in your application. Previously, the code would block the UI thread until it was ready to show the complete UI (after the DB call). What you're doing by making it asynchronous is freeing up that UI thread. But then the UI has to show something. So what you really have is a new "loading" kind of state in your app.
So, the best solution is to have your constructor (synchronously) initialize to the loading state, and then have it update to the loaded state. I wrote an article about this technique - it uses MVVM/WPF, but the concepts are the same.
The natural way to do this is to use async/await rather than ContinueWith:
public MyGui()
{
// some code
Loaded = ContinueConstructionAsync();
async Task ContinueConstructionAsync()
{
await InitializeMyGui();
// some more code
}
}
public Task Loaded { get; }
This code also adds a new property that can be awaited if other parts of the code depend on this type being loaded.
Make the constructor private, and use a static factory method to call it and your initialization code. A static method can of course be async.
class MyGui
{
private MyGui()
{
}
private async Task InitializeMyGui()
{
}
static public async Task<MyGui> CreateInstance()
{
var instance = new MyGui();
await instance.InitializeMyGui();
return instance;
}
}
Now you can instantiate an instance using async code:
var myInstance = MyGui.CreateInstance();
We have a WPF dialog library, which exposes an async Task ShowAsync(...) method, library has to be used. Around it, we've built a MVVM-based singleton service, DialogService with our own async ShowAsync method, and view models call it when needed. The problem is, the library does not support showing more than one dialog at the time and we have to keep the dispatcher thread working, so if another operation requests a dialog before the user closes the first one, the library throws an exception, which then cascades into another dialog call, so on and so on.
So we need to implement some sort of queueing, in the sense that second task cannot even begin (be cold?) until the first task is completed. It all has to happen in dispatcher thread, but on a plus not the ShowAsync is always called from the dispatcher thread and we use ConfigureAwait(true) on calling library method.
Some of the calls to dialog service have their own ContinueWith constructs, if that is important.
I've seen some solutions like SerialQueue et al, but they all deal with serializing tasks in general, without caring on what context and thread they run, we need a more WPFy solution where everything runs on dispatcher thread without making it unresponsive.
Any ideas would be welcome.
I solved it with a semaphore with a single choke point, the dialogs will queue up waiting for the semaphore to release. This solution seems more in line with await/async philosophy:
internal class DialogService : IDialogService
{
private readonly DispatcherSynchronizationContextAwaiter uiContextAwaiter;
private readonly SemaphoreSlim dialogSemaphore = new(1);
public DialogService(DispatcherSynchronizationContextAwaiter uiContextAwaiter)
{
this.uiContextAwaiter = uiContextAwaiter;
}
public async Task<DialogResult> ShowDialogAsync(string title, string message, DialogType dialogType = DialogType.Information, DialogButtons dialogButtons = DialogButtons.OK)
{
await dialogSemaphore.WaitAsync();
try
{
await uiContextAwaiter;
var result = await DialogHost.Show(new DialogViewModel {Title = title, Message = message, DialogType = dialogType, DialogButtons = dialogButtons});
return (DialogResult?) result ?? DialogResult.OK;
}
finally
{
dialogSemaphore.Release();
}
}
}
DispatcherSynchronizationContextAwaiter is not fundamentally important for my problem, but it does allow the ShowDialogAsync to be called from any thread. It simply posts a continuation on the dispatcher thread. I took this code from Thomas Levesque's blog from 2015, and adjusted to my needs. This is the source if you need it:
internal class DispatcherSynchronizationContextAwaiter: INotifyCompletion
{
private static readonly SendOrPostCallback postCallback = state => ((Action)state)?.Invoke();
private readonly SynchronizationContext context;
public DispatcherSynchronizationContextAwaiter(SynchronizationContext context)
{
this.context = context;
}
public bool IsCompleted => context == SynchronizationContext.Current;
public void OnCompleted(Action continuation) => context.Post(postCallback, continuation);
public void GetResult() { }
// clone yourself on GetAwait
public DispatcherSynchronizationContextAwaiter GetAwaiter() => new(context);
}
I'm new in the themes tasks.
How to get access to the right task and invoke the method on this task? The task has an ID and I gave a Name to it and I got a TaskScheduler.
I get the Exception, which is correct:
InvalidOperationException: The calling thread can not access this object because the object is owned by another thread.
Problem:
In the Main I start a Task with CustomSplashScreenViewModel.StartSplashScreenAsync(). More Tasks get startet and are running. With the Event when everything has loaded I need to Close my SplashScreen.
The method CustomSplashScreenViewModel.CloseSplash(_splashTask, _taskSchedulerSplash); get those informations. When I debug it, the _splashTask is "null" and in the _taskSchedulerSplash it has the _splashTask inside.
Inside the CloseSplash Method I like to invoke the method _view.Close() on the _splashTask.
How do I solve this?
public class Program
{
private static readonly SingleThreadTaskScheduler _taskSchedulerSplash = new SingleThreadTaskScheduler(ApartmentState.STA);
[ThreadStatic]
private static Task _splashTask;
// [STAThread]
public static async Task Main()
{
_splashTask = await Task.Factory.StartNew(() =>
{
return CustomSplashScreenViewModel.StartSplashScreenAsync();
}, _taskSchedulerSplash);
var taskScheduler = new SingleThreadTaskScheduler(ApartmentState.STA);
var taskList = new List<Task>();
var updateTask = Task.Run(InstallHelper.CheckForUpdatesAsync);
updateTask.Wait();
taskList.Add(updateTask);
var tasks = await Task.Factory.ContinueWhenAll(taskList.ToArray(), result => Task.Factory.StartNew( ()=>AppStart(container), taskScheduler));
tasks.Wait();
}
private static void App_MainWindowLoaded(object sender, EventArgs e)
{
CustomSplashScreenViewModel.CloseSplash(_splashTask, _taskSchedulerSplash);
}
}
public class CustomSplashScreenViewModel
{
private static Thread _currentThread;
public static void CloseSplash(Task task, TaskScheduler taskScheduler)
{
if (_view!= null)
{
// Here i need the right Task and to invoke the following method
// if I'm doing _view.Dispatcher.Invoke(new Action(_view.Close)); it doesn't
// work coz it's another STA Thread. Inside this class I can store the right task in
// a local variable. this works. But how to invoke it if the Task is already running?
// I like to overgive the task - may it isn't possible.
_view.Close();
}
}
}
Thanks a lot for helping me.
You can't change a running task. However, you can schedule another task that will run on the same scheduler, which should work just fine for what you need (it'll run on the same STA thread that owns the splash screen):
public static void CloseSplash(Task task, TaskScheduler taskScheduler)
{
if (_view!= null)
{
Task.Factory.StartNew(() => _view.Close(), taskScheduler);
}
}
On a side note, I'd recommend these to make the code easier to understand:
Introducing a TaskFactory instance for your custom task scheduler instead of using TaskScheduler with Task.Factory.
Avoid Task.Factory.ContinueWhenAll and Task.Wait; use Task.WhenAll and await instead.
I don't know if I am doing something wrong or I found a bug in the Async library, but I have seen an issue when running some async code after I came back to the Synchronized context with continueWith().
UPDATE: The code now runs
using System;
using System.ComponentModel;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
internal static class Program
{
[STAThread]
private static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
MainFrameController controller = new MainFrameController(this);
//First async call without continueWith
controller.DoWork();
//Second async call with continueWith
controller.DoAsyncWork();
}
public void Callback(Task<HttpResponseMessage> task)
{
Console.Write(task.Result); //IT WORKS
MainFrameController controller =
new MainFrameController(this);
//third async call
controller.DoWork(); //IT WILL DEADLOCK, since ConfigureAwait(false) in HttpClient DOESN'T change context
}
}
internal class MainFrameController
{
private readonly Form1 form;
public MainFrameController(Form1 form)
{
this.form = form;
}
public void DoAsyncWork()
{
Task<HttpResponseMessage> task = Task<HttpResponseMessage>.Factory.StartNew(() => DoWork());
CallbackWithAsyncResult(task);
}
private void CallbackWithAsyncResult(Task<HttpResponseMessage> asyncPrerequisiteCheck)
{
asyncPrerequisiteCheck.ContinueWith(task =>
form.Callback(task),
TaskScheduler.FromCurrentSynchronizationContext());
}
public HttpResponseMessage DoWork()
{
MyHttpClient myClient = new MyHttpClient();
return myClient.RunAsyncGet().Result;
}
}
internal class MyHttpClient
{
public async Task<HttpResponseMessage> RunAsyncGet()
{
HttpClient client = new HttpClient();
return await client.GetAsync("https://www.google.no").ConfigureAwait(false);
}
}
partial class Form1
{
private IContainer components;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Text = "Form1";
}
#endregion
}
}
The HttpClient code which is async runs well the first time.
Then, I run the second async code and return to the UI context with ContinueWith, and it works well.
I run the HttClient code again, but it deadlock because this time ConfigureAwait(false) does not change the context.
The main problem in your code is due to StartNew and ContinueWith. ContinueWith is dangerous for the same reasons that StartNew is dangerous, as I describe on my blog.
In summary: StartNew and ContinueWith should only be used if you're doing dynamic task-based parallelism (which this code is not).
The actual problem is that HttpClient.GetAsync doesn't use (the equivalent of) ConfigureAwait(false); it's using ContinueWith with its the default scheduler argument (which is TaskScheduler.Current, not TaskScheduler.Default).
To explain in more detail...
The default scheduler for StartNew and ContinueWith is not TaskScheduler.Default (the thread pool); it's TaskScheduler.Current (the current task scheduler). So, in your code, DoAsyncWork as it currently is does not always execute DoWork on the thread pool.
The first time DoAsyncWork is called, it will be called on the UI thread but without a current TaskScheduler. In this case, TaskScheduler.Current is the same as TaskScheduler.Default, and DoWork is called on the thread pool.
Then, CallbackWithAsyncResult invokes Form1.Callback with a TaskScheduler that runs it on the UI thread. So, when Form1.Callback calls DoAsyncWork, it is called on the UI thread with a current TaskScheduler (the UI task scheduler). In this case, TaskScheduler.Current is the UI task scheduler, and DoAsyncWork ends up calling DoWork on the UI thread.
For this reason, you should always specify a TaskScheduler when calling StartNew or ContinueWith.
So, this is a problem. But it's not actually causing the deadlock you're seeing, because ConfigureAwait(false) should allow this code to just block the UI instead of deadlocking.
It's deadlocking because Microsoft made the same mistake. Check out line 198 here: GetContentAsync (which is called by GetAsync) uses ContinueWith without specifying a scheduler. So, it's picking up the TaskScheduler.Current from your code, and will not ever complete its task until it can run on that scheduler (i.e., the UI thread), causing the classic deadlock.
There's nothing you can do to fix the HttpClient.GetAsync bug (obviously). You'll just have to work around it, and the easiest way to do that is to avoid having a TaskScheduler.Current. Ever, if you can.
Here's some general guidelines for asynchronous code:
Don't ever use StartNew. Use Task.Run instead.
Don't ever use ContinueWith. Use await instead.
Don't ever use Result. Use await instead.
If we just do minimal changes (replacing StartNew with Run and ContinueWith with await), then DoAsyncWork always executes DoWork on the thread pool, and the deadlock is avoided (since await uses the SynchronizationContext directly and not a TaskScheduler):
public void DoAsyncWork()
{
Task<HttpResponseMessage> task = Task.Run(() => DoWork());
CallbackWithAsyncResult(task);
}
private async void CallbackWithAsyncResult(Task<HttpResponseMessage> asyncPrerequisiteCheck)
{
try
{
await asyncPrerequisiteCheck;
}
finally
{
form.Callback(asyncPrerequisiteCheck);
}
}
However, it's always questionable to have a callback scenario with Task-based asynchrony, because Tasks themselves have the power of callbacks within them. It looks like you're trying to do a sort of asynchronous initialization; I have a blog post on asynchronous construction that shows a few possible approaches.
Even something really basic like this would be a better design than callbacks (again, IMO), even though it uses async void for initialization:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
MainFrameController controller = new MainFrameController();
controller.DoWork();
Callback(controller.DoAsyncWork());
}
private async void Callback(Task<HttpResponseMessage> task)
{
await task;
Console.Write(task.Result);
MainFrameController controller = new MainFrameController();
controller.DoWork();
}
}
internal class MainFrameController
{
public Task<HttpResponseMessage> DoAsyncWork()
{
return Task.Run(() => DoWork());
}
public HttpResponseMessage DoWork()
{
MyHttpClient myClient = new MyHttpClient();
var task = myClient.RunAsyncGet();
return task.Result;
}
}
Of course, there's other design problems here: namely, DoWork is blocking on a naturally-asynchronous operation, and DoAsyncWork is blocking a thread pool thread on a naturally-asynchronous operation. So, when Form1 calls DoAsyncWork, it's awaiting a thread pool task that's blocked on an asynchronous operation. Async-over-sync-over-async, that is. You may also benefit from my blog series on Task.Run etiquette.
Don't use .Result. If you have any code at all that uses async/await, just completely forget it even exists. Even if we get it working today, what you are trying to do will be so incredibly brittle that it won't necessarily work tomorrow.
In wpf I have two windows in separate threads.
From Window A' in Thread A I would like to start a Task in Tread B of Window B' and await the return value in Thread A.
It suppose it is possible but how?
Do you know an example perhaps?
Multiple WPF UI threads is a pretty advanced scenario.
You should be able to do it by having Thread B expose a task factory:
TaskFactory _taskFactory;
public TaskFactory TaskFactory { get { return _taskFactory; } }
At some point in Thread B's startup do something like this:
// Startup code running on Thread B
_taskFactory = new TaskFactory(
TaskScheduler.FromCurrentSynchronizationContext());
Then you can consume the factory from Thread A, allowing Thread A (or anyone) to queue work to Thread B:
await _threadB.TaskFactory.StartNew(() =>
{
...
});
As I understand it, the actual problem is how to initiate an action on Window B from Window A, when both windows run in separate threads.
In this case, you can use SynchronizationContext.Post or SynchronizationContext.Send to start the action on the second window's synchronization context without starting a task. Send will block the calling thread until the callback is processed, while Post will return immediatelly. This makes Post better suited to the scenario.
The end result is the same as creating a Task on Thread B. The callback will execute on the Thread B, but now you avoid the work of creating and dispatching a Task and it requires somewhat less code.
To make this tidy, you can store a reference to the current Synchronization context when you create a new instance of Window B and use it in a wrapper method in Windows B:
public partial class WindowB : Window
{
private SynchronizationContext _context;
...
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
_context = SynchronizationContext.Current;
}
public void PostAction(string data)
{
_context.Post(ActualAction,data);
}
private void ActualAction(object state)
{
Title = state.ToString();
}
}