Using Task and actions for simple threading?

Posted on

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

Leave a Reply

Your email address will not be published. Required fields are marked *