Problem
My question is regarding a rather inelegant solution to a problem that I came up with a while ago.
I was making a Winform Application to access active directory among other things and needed to thread my application to stop my UI from freezing.
Although I have little knowledge of actual threading (I kept hitting cross-thread access issues), I’ve built a little function that allowed me to pass an object, act on it in a separate thread and then once it returned to the UI CurrentContext I could act on it again to update the view.
TL;DR
The below function sends a Generic Item to a separate thread, then once acted upon, returns the item and acts on it again.
public static void doThreadedQuery<T>(Func<T> processQuery, Action<T> onComplete)
{
TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task<T> doQuery = new Task<T>(processQuery);
doQuery.ContinueWith((results) => { if (onComplete != null) onComplete(results.Result); }, scheduler);
doQuery.Start();
}
An example usage for something similar is pass an activeDirectory
SearchResultCollection
, parse through it and generate a TreeView/Grid. Then return the populated grid and UI updated without freezing.
Now I have done variants with a progress function/cancellation token etc, but I suppose my question is: on a base level, is there some horrible inefficiency or simple equivalent I am unaware of (aside from the Background Worker I am not overly fond of)?
I feel like I’m re-inventing the wheel here.
Solution
You shouldn’t usually use Task
constructor directly, use Task.Factory.StartNew
(or Run
).
I also don’t understand why you want to have a separate method for starting a task. It is completely superficial. It’s just as useless as adding Print
method that calls Console.WriteLine
instead of using Console.WriteLine
directly.
Normally, you just do this:
void SomeUICode ()
{
Task.Factory.StartNew (() => {
// This will run on thread pool
return DoSomeExpensiveWork ();
}).ContinueWith (result => {
// This will run on UI thread
UpdateUI (result);
}, TaskScheduler.FromCurrentSynchronizationContext ());
}
It’s basically the same thing you wrote, but without abstracting it away doThreadedQuery
since there’s nothing to abstract away here.
However, if you don’t mind using C# 5, you can simplify things with async
/await
operators:
async void SomeUICode ()
{
var result = await Task.Factory.Run (() => {
// This will run on thread pool
return DoSomeExpensiveWork ();
});
// The rest of this method will be scheduled on UI thread:
UpdateUI (result);
}
Note the await
keyword that “splits” the method and schedules the rest of it as a continuation on current synchronization context (exactly the same thing you did manually).
I declared the function as async void
but you should only do this for event handlers (which have to be void
). In all other cases, declare the function as async Task
so the calling code has a chance to wait for it to finish:
async Task SomeUICode ()
{
// ...
}
On a side note, doThreadedQuery
is a rather unfortunate name because
- .NET method names should be PascalCase, not camelCase;
- There’s no “query” being performed, you’re just scheduling an operation.