I would like to ask you on your opinion about the correct architecture when to use Task.Run
. I am experiencing laggy UI in our WPF .NET 4.5 application (with Caliburn Micro framework).
Basically I am doing (very simplified code snippets):
public class PageViewModel : IHandle<SomeMessage> { ... public async void Handle(SomeMessage message) { ShowLoadingAnimation(); // Makes UI very laggy, but still not dead await this.contentLoader.LoadContentAsync(); HideLoadingAnimation(); } } public class ContentLoader { public async Task LoadContentAsync() { await DoCpuBoundWorkAsync(); await DoIoBoundWorkAsync(); await DoCpuBoundWorkAsync(); // I am not really sure what all I can consider as CPU bound as slowing down the UI await DoSomeOtherWorkAsync(); } }
From the articles/videos I read/saw, I know that await
async
is not necessarily running on a background thread and to start work in the background you need to wrap it with await Task.Run(async () => ... )
. Using async
await
does not block the UI, but still it is running on the UI thread, so it is making it laggy.
Where is the best place to put Task.Run?
Should I just
Wrap the outer call because this is less threading work for .NET
, or should I wrap only CPU-bound methods internally running with Task.Run
as this makes it reusable for other places? I am not sure here if starting work on background threads deep in core is a good idea.
Ad (1), the first solution would be like this:
public async void Handle(SomeMessage message) { ShowLoadingAnimation(); await Task.Run(async () => await this.contentLoader.LoadContentAsync()); HideLoadingAnimation(); } // Other methods do not use Task.Run as everything regardless // if I/O or CPU bound would now run in the background.
Ad (2), the second solution would be like this:
public async Task DoCpuBoundWorkAsync() { await Task.Run(() => { // Do lot of work here }); } public async Task DoSomeOtherWorkAsync( { // I am not sure how to handle this methods - // probably need to test one by one, if it is slowing down UI }
Async methods are intended to be non-blocking operations. An await expression in an async method doesn't block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method.
If you use Task. Run with an I/O operation, you're creating a thread (and probably occupying a CPU core) that will mostly be waiting. It may be a quick and easy way to keep your application responsive, but it's not the most efficient use of system resources. A much better approach is to use await without Task.
NET, Task. Run is used to asynchronously execute CPU-bound code.
So when you hit the await , the control flow is returned to the calling method and execution of your async method is resumed after the await when the awaited Task has finished. As there is no more code after your await , there is no need to use await anyway. Simply return the Task is enough.
Note the guidelines for performing work on a UI thread, collected on my blog:
There are two techniques you should use:
1) Use ConfigureAwait(false)
when you can.
E.g., await MyAsync().ConfigureAwait(false);
instead of await MyAsync();
.
ConfigureAwait(false)
tells the await
that you do not need to resume on the current context (in this case, "on the current context" means "on the UI thread"). However, for the rest of that async
method (after the ConfigureAwait
), you cannot do anything that assumes you're in the current context (e.g., update UI elements).
For more information, see my MSDN article Best Practices in Asynchronous Programming.
2) Use Task.Run
to call CPU-bound methods.
You should use Task.Run
, but not within any code you want to be reusable (i.e., library code). So you use Task.Run
to call the method, not as part of the implementation of the method.
So purely CPU-bound work would look like this:
// Documentation: This method is CPU-bound. void DoWork();
Which you would call using Task.Run
:
await Task.Run(() => DoWork());
Methods that are a mixture of CPU-bound and I/O-bound should have an Async
signature with documentation pointing out their CPU-bound nature:
// Documentation: This method is CPU-bound. Task DoWorkAsync();
Which you would also call using Task.Run
(since it is partially CPU-bound):
await Task.Run(() => DoWorkAsync());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With