Using await Task.Run(() = someMethodAsync()) vs await someMethodAsync() in a UI based app
By default, when you await
an incomplete operation, the sync-context is captured (if present). Then, when reactivated by the completion of the async work, that captured sync-context is used to marshal the work. Many scenarios don't have a sync-context, but UI applications such as winforms and WPF do, and the sync-context represents the UI thread (basically: this is so that the default behaviour becomes "my code works" rather than "I get cross-thread exceptions when talking to the UI in my async
method"). Essentially, this means that when you await Task.Delay(1000)
from a UI thread, it pauses for 1000ms (releasing the UI thread) but then resumes on the UI thread. This means that you "Tons of work to do in here" happens on the UI thread, blocking it.
Usually, the fix for this is simple: add .ConfigureAwait(false)
, which disables sync-context capture:
await Task.Delay(1000).ConfigureAwait(false);
(usually: add this after all await
in that method)
Note that you should only do this when you know that you don't need what the sync-context offers. In particular: that you aren't going to try to touch the UI afterwards. If you do need to talk to the UI, you'll have to use the UI dispatcher explicitly.
When you used Task.Run(...)
, you escaped the sync-context via a different route - i.e. by not starting on the UI thread (the sync-context is specified via the active thread).
Using async with c# windows form
Don't block on async code. Instead, use await
:
private async void searchButton_Click(object sender, EventArgs e)
{
await RunAsync();
...
}
Side note: this code is using async void
because the method is an event handler. Normally, you would want to avoid async void
.
How do I marshal an event from Task.Run back to the UI thread?
Since UpdateSomethingAsync
needs to access the UI context, it shouldn't be wrapped in a Task.Run
call. (You should very rarely, need to call an async
method from Task.Run
, usually only if the method is implemented incorrectly and you can't fix it.)
Instead DoSomethingSynchronous
should be the thing you call from Task.Run
. After all, the purpose of that method is to asynchronously run a synchronous method in a thread pool thread. So only use it for the synchronous method you want run in a thread pool thread, not the (supposedly) asynchronous method that needs to access the UI context.
Await after creating WinForm UI Control within a new Task causes deadlock?
First of all, you should be using Task.ConfigureAwait(false)
in every place you don't want await
to resume on the current synchronization context. If you did so for your Task.Delay
call, you would not have that issue (but most probably a different one).
However if I create the UI Control using Invoke method as shown below it doesn't dead lock. I am curious why? Since the task is not running in UI context, what prevents Task.Delay from resuming in above code?
In fact the way you use it, Task.Delay
will try to resume at the whatever the current synchronization context is when you invoke it. And here is the point. Normally the thread pool threads do not have a synchronization context (or use the default one which effective does not do any synchronization). But every System.Windows.Forms.Control
by default installs (sets SynchronizationContext.Current
to) a WindowsFormsSynchronizationContext in it's constructor. Hence after the call
Program.Container.GetInstance<FinishedRequestItem>()
the current synchronization context is changed, and then Task.Delay
tries to resume on that context, but of course it can't because there is no message pump running on that thread (which is required for WindowsFormsSynchronizationContext
to function properly).
In general you should not create UI elements on non UI threads.
Updating GUI from async method
Neither Task
nor await
give you any guarantees in this respect. You need to consider the context where the task was created, and where the continuation was posted.
If you're using await
in a winforms event handler, the synchronization context is captured, and the continuation returns back to the UI thread (in fact, it pretty much calls Invoke
on the given block of code). However, if you just start a new task with Task.Run
, or you await
from another synchronization context, this no longer applies. The solution is to run the continuation on the proper task scheduler you can get from the winforms synchronization context.
However, it should be noted that it still doesn't necessarily mean that async
events will work properly. For example, Winforms also uses events for things like CellPainting
, where it actually depends on them running synchronously. If you use await
in such an event, it's pretty much guaranteed not to work properly - the continuation will still be posted to the UI thread, but that doesn't necessarily make it safe. For example, suppose that the control has code like this:
using (var graphics = NewGraphics())
{
foreach (var cell in cells)
CellPainting(cell, graphics);
}
By the time your continuation runs, it's entirely possible the graphics
instance has already been disposed of. It's even possible the cell is no longer part of the control, or that the control itself no longer exists.
Just as importantly, the code might depend on your code changing things - for example, there's events where you set some value in their EventArgs
to indicate e.g. success, or to give some return value. Again, this means you can't use await
inside - as far as the caller is aware, the function just returned the moment you do the await
(unless it completes synchronously).
access all ListView Items from Task.Run() gives this error InvalidOperationException: 'Cross-thread operation not valid
The error is occurring because you are trying to read the items from the ListView from within the Task, which is running on another thread. Instead of reading the items within the Task.Run call, copy them into an array on the line above Task.Run and reference that array instead.
The CopyTo method provides an easy way to copy the ListViewItems to an array. It would look something like this:
// instantiate new array to hold copy of items
var copyOfItems = new ListViewItems[lvwFiles.Items.Count];
// copy items
lvwFiles.Items.CopyTo(copyOfItems, 0);
Now just reference copyOfItems
within your Task!
C# Winforms - Why are some controls updated in async event handler, but not all of them?
Thanks to the insightful comments, a working fix to this issue is the following:
// add this to unbind data source before modifying data
myGrid.DataSource = null;
// async method call that modifies data
await MyTask(progress);
Note that just clearing the data bindings did not work in my case:
myGrid.DataBindings.Clear(); // not working
How to provide a feedback to UI in a async method?
First of all, say no to async void
methods (Event handlers are exceptions) because exceptions thrown inside it will not be noticed. Instead use async Task
and await it.
private async void button1_Click(object sender, EventArgs e)// <--Note the async modifier
{
// almost 15 process
foreach (var process in Processes)
{
// call a async method to process
await ProcessObject(process);
}
}
private async Task ProcessObject(ProcessViewModel process)// <--Note the return type
{
// my code is here with some loops
await Task.Run(()=>
{
//Will be run in ThreadPool thread
//Do whatever cpu bound work here
});
//At this point code runs in UI thread
process.Progress++;
// feedback to UI
UpdateRow(process);
}
However, this will start only one task at a time, once that is done it will start next. If you want to start all of them at once you can start them and use Task.WhenAll
to await it.
private async void button1_Click(object sender, EventArgs e)// <--Note the async modifier
{
var tasks = Processes.Select(ProcessObject).ToList();
await Task.WhenAll(tasks);
}
Related Topics
How to Have an Optional Parameter for an ASP.NET Soap Web Service
The Remote Server Returned an Unexpected Response: (413) Request Entity Too Large
Convert String Value to Operator in C#
Is .Net 4.0 Compatible with Windows Xp Sp2 or Below
Using Sse in C# Is It Possible
Reading Fromuri and Frombody at the Same Time
How to Use Extension Methods and Linq in .Net 2.0 or 3.0
Use "Real" Cultureinfo.Currentculture in Wpf Binding, Not Cultureinfo from Ietflanguagetag
Is There a Quick Way to Get the Control That's Under the Mouse
Wpf Dispatcher {"The Calling Thread Cannot Access This Object Because a Different Thread Owns It."}
C# - How to Copy a Single Excel Worksheet from One Workbook to Another
Exclude a Field/Property from the Database with Entity Framework 4 & Code-First
How to Insert Characters to a File Using C#
Azure Key Vault: Access Denied
What Thread Runs the Code After the 'Await' Keyword
Why Would Try/Finally Rather Than a "Using" Statement Help Avoid a Race Condition
Linq to Objects - Return Pairs of Numbers from List of Numbers