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\$
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

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I think he was just indicating (by example name) a process running a structured Query language operation. But otherwise, nice examples! \$\endgroup\$ – Spiked3 Jan 6 '14 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 Jan 6 '14 at 12:34
  • 1
    \$\begingroup\$ async void should be avoided (in most cases) \$\endgroup\$ – DaveShaw Jan 6 '14 at 12:46
  • \$\begingroup\$ @Dave: Thanks for the remark, I amended my answer. \$\endgroup\$ – Dan Jan 6 '14 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\$ – apieceoffruit Jan 6 '14 at 17:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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