3

Consider this Windows Forms code (one could write similar WPF analogue):

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void TraceThreadInfo([CallerMemberName]string callerName = null)
    {
        Trace.WriteLine($"{callerName} is running on UI thread: {!this.InvokeRequired}");
    }

    private void DoCpuBoundWork([CallerMemberName]string callerName = null)
    {
        TraceThreadInfo(callerName);
        for (var i = 0; i < 1000000000; i++)
        {
            // do some work here
        }
    }

    private async Task Foo()
    {
        DoCpuBoundWork();
        await Bar();
    }

    private async Task Bar()
    {
        DoCpuBoundWork();
        await Boo();
    }

    private async Task Boo()
    {
        DoCpuBoundWork();
        // e.g., saving changes to database
        await Task.Delay(1000);
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        TraceThreadInfo();
        await Foo();
        Trace.WriteLine("Complete.");
        TraceThreadInfo();
    }
}

Here's the chain of Foo/Bar/Boo methods, which I want to execute asynchronously, without blocking of UI thread. These methods are similar, in sense that all of them makes some CPU-bound work and ultimately calls "true" asynchronous operation (e.g., perform some heavy calculations an save result to the database).

The output from the code above is this:

button1_Click is running on UI thread: True
Foo is running on UI thread: True
Bar is running on UI thread: True
Boo is running on UI thread: True
Complete.
button1_Click is running on UI thread: True

So, all this stuff executes synchronously.
I know about capturing of current context by built-in awaitables. So, I thought, that it will be enough to call ConfigureAwait(false) like this:

    private async Task Foo()
    {
        await Task.Delay(0).ConfigureAwait(false);
        DoCpuBoundWork();
        await Bar();
    }

but, actually, this doesn't change anything.
I'm wondering, how this can be "pushed" to thread pool thread, assuming, that at the end of button1_Click method I need to return to UI thread?

Edit.

Task.Delay(0) actually optimizes call, when its argument is 0 (thanks to @usr for the note). This:

    private async Task Foo()
    {
        await Task.Delay(1).ConfigureAwait(false);
        DoCpuBoundWork();
        await Bar();
    }

will works, as expected (everything executes on thread pool, except button1_Click's code). But this is even worse: to capture context or to not capture depends on awaitable implementation.

Dennis
  • 37,026
  • 10
  • 82
  • 150
  • See ["Why was “SwitchTo” removed from Async CTP / Release?"](http://stackoverflow.com/questions/15363413/why-was-switchto-removed-from-async-ctp-release). I asked [a similar question](http://stackoverflow.com/q/18779393/1768303) awhile ago when learning`async/await`. I totally agree with the outcome that using `Task.Run` is a much cleaner approach. – noseratio Aug 27 '15 at 22:55

2 Answers2

7

Your await Task.Delay(0).ConfigureAwait(false); is a poor mans Task.Yield() attempt (which does not work because I guess that Delay optimizes itself out if the argument is zero).

I would not recommend yield here.

I think in your click handler you should push to the thread-pool:

await Task.Run(async () => await Foo());

This is very simple and always works if the method you are calling does not depend on the synchronization context of the UI. This is architecturally nice because non of the methods you are calling need or should be aware by what code they are called.

usr
  • 168,620
  • 35
  • 240
  • 369
  • `Task.Delay(0)`/`Task.Yield()` is just a sample (btw, `ConfigureAwait` can't be called for `Yield`). Maybe, `Task.Run` is an option, but I'd like to avoid mixing of `Task.Run`/`Task.Factory.StartNew` with "pure" `async`/`await` code. – Dennis Aug 27 '15 at 11:42
  • Thanks for the note about possible `TaskDelay(0)` optimization. I've edited the question. – Dennis Aug 27 '15 at 11:52
  • Why don't you want to mix that? Pushing the CPU stuff to the thread-pool seems to be your goal. This answer shows how to do that in a clean way. Also, this is the fastest option possible to do that. – usr Aug 27 '15 at 11:55
  • 1
    Basically, because I'm expecting, that `async` method will be... well, `async`. Otherwise we're getting some nonsense - method *is* declared as async, it *awaits* other awaitables, but runs *synchronously*. All of the samples about `async`/`await` are so easy - just handle `button_click`/`ICommand.Execute`, and you'll get non-blocking UI. Instead of this we've got an API, that can't be used 1-1 comparing to "old" `Task.Factory.StartNew` code (e.g., when porting it to `async`/`await`). – Dennis Aug 27 '15 at 12:09
  • 1
    Actually your example could be reduced to: `await Task.Run(() => Foo());`. Since the whole `async` stuff is in the end just a function returning a `Task`. In this case you can return the `Task` from `Foo()` directly. The only case where your example would be required, is if `Foo()` would not return a `Task` but any other awaitable object. – Nitram Aug 27 '15 at 12:13
  • @usr: It's sad, but looks like `Task.Run` is the best way. I'll accept the answer. One thing I completely disagree: architecturally this is a bulls...t, sorry. But anyway, thanks. – Dennis Aug 27 '15 at 12:14
  • @Dennis neither async nor await promise low latency. This is a convention thing. I think you should treat Foo like a library. This means that it is *not* the job of Foo to push to the thread pool (async over sync). It is the job of Foo to be efficient. If you require low latency make the UI layer push. – usr Aug 27 '15 at 12:15
0

Task.Factory.StartNew() can be used to easily run code in a separate thread. You can optionally make the current thread wait for the second thread by calling Wait().

Below is a quick sample program which demonstrates the relevant pieces:

class Program
{
    static void Main(string[] args)
    {
        Task t = Task.Factory.StartNew(() =>
        {
            //New thread starts here.
            LongRunningThread();
        });
        Console.WriteLine("Thread started");
        t.Wait(); //Wait for thread to complete (optional)
        Console.WriteLine("Thread complete");
        Console.ReadKey();
    }

    static void LongRunningThread()
    {
        Console.WriteLine("Doing work");
        //do work here
    }
}
Greg Sansom
  • 20,442
  • 6
  • 58
  • 76
  • `Task.Factory.StartNew` isn't an option here, because, as far as I understand, this will be clear sample of "async over sync". `Task.Run` - yes, maybe (see comment to the @usr answer), it allows to pass async delegates. – Dennis Aug 27 '15 at 11:44