6
\$\begingroup\$

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.

\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

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

\$\endgroup\$
5
  • \$\begingroup\$ I think he was just indicating (by example name) a process running a structured Query language operation. But otherwise, nice examples! \$\endgroup\$
    – Spiked3
    Commented Jan 6, 2014 at 12:25
  • \$\begingroup\$ @Spiked: Perhaps, but his method doesn't really “know” whether function passed to it does a LINQ query or something else, so it's still a bad naming decision. \$\endgroup\$
    – Dan
    Commented Jan 6, 2014 at 12:34
  • 1
    \$\begingroup\$ async void should be avoided (in most cases) \$\endgroup\$
    – DaveShaw
    Commented Jan 6, 2014 at 12:46
  • \$\begingroup\$ @Dave: Thanks for the remark, I amended my answer. \$\endgroup\$
    – Dan
    Commented Jan 6, 2014 at 12:49
  • \$\begingroup\$ I forgot to change the example name when I took it from the project, it was doing an ActiveDirectory query and T was defined hence the name. \$\endgroup\$ Commented Jan 6, 2014 at 17:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.