9

I'm trying to understand when to use TaskEx.Run. I have provided two code sample i wrote below that produce the same result. What i fail to see is why i would take the Task.RunEx TaskEx.RunEx approach, I'm sure there is a good reason and was hoping someone could fill me in.

async Task DoWork(CancellationToken cancelToken, IProgress<string> progress)
{
    int i = 0;
    TaskEx.RunEx(async () =>
        {
            while (!cancelToken.IsCancellationRequested)
            {
                progress.Report(i++.ToString());
                await TaskEx.Delay(1, cancelToken);
            }
        }, cancelToken);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
    if (button.Content.ToString() == "Start")
    {
        button.Content = "Stop";
        cts.Dispose();
        cts = new CancellationTokenSource();
        listBox.Items.Clear();
        IProgress<string> progress = new Progress<string>(s => 
        {
            listBox.Items.Add(s); 
            listBox.ScrollIntoView(listBox.Items[listBox.Items.Count - 1]);
        });
        DoWork(cts.Token, progress);
    }
    else
    {
        button.Content = "Start";
        cts.Cancel();
    }
}

I can achieve the same results like so

  async Task DoWork(CancellationToken cancelToken)
    {
        int i = 0;
        while (!cancelToken.IsCancellationRequested)
        {
            listBox.Items.Add(i++);
            listBox.ScrollIntoView(listBox.Items[listBox.Items.Count - 1]);
            await TaskEx.Delay(100, cancelToken);

        }
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if (button.Content.ToString() == "Start")
        {
            button.Content = "Stop";
            cts.Dispose();
            cts = new CancellationTokenSource();
            listBox.Items.Clear();
            DoWork(cts.Token);
        }
        else
        {
            button.Content = "Start";
            cts.Cancel();
        }
    }
poco
  • 2,935
  • 6
  • 37
  • 54
  • The above thread is a discussion on the reasons for TaskEx.RunEx, its all to do with changes that could not be put into the core .NET functionality for the CTP, but will be integrated properly for final release – Patrick McDonald Feb 17 '12 at 20:27
  • Changed `Task.RunEx` to `TaskEx.RunEx` There is neither `Run()` nor `RunEx()` in Async CTP `Task` class. They are both in `TaskEx`. Correct me, if I am wrong – Gennady Vanin Геннадий Ванин Apr 10 '13 at 04:35

3 Answers3

13

Use TaskEx.Run when you want to run synchronous code in a thread pool context.

Use TaskEx.RunEx when you want to run asynchronous code in a thread pool context.

Stephen Toub has two blog posts related to the difference in behavior:

This is only one of several options you have for creating tasks. If you do not have to use Run/RunEx, then you should not. Use simple async methods, and only use Run/RunEx if you need to run something in the background.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Actually that's not true... RunEx has to do with value returning lambda expressions... not asynchrony. Run handles async lambda's just fine. – Firoso Feb 17 '12 at 20:41
  • 2
    In .Net 4.5 DP, there seems to be only `Task.Run()`, no `Ex`es. – svick Feb 17 '12 at 22:43
  • 6
    TaskEx is in the CTP only. In .Net 4.5 the methods on TaskEx have been integrated into Task. – Phil Feb 17 '12 at 23:34
  • 1
    @svick, @Phil: In .NET 4.5, `TaskEx.Run` becomes an overload of `Task.Run`, and `TaskEx.RunEx` also becomes an overload of `Task.Run`. – Stephen Cleary Feb 18 '12 at 00:18
  • 1
    @Firoso: `RunEx` performs an unwrapping of the inner task. If you pass an async lambda to `Run` in VS2010, it *will* work "just fine" -- if you don't care about when it completes. However, if you want the returned `Task` to complete when the async lambda completes, then use `TaskEx`. See [this blog post](http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx) or [this blog post](http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx) for more info. – Stephen Cleary Feb 18 '12 at 00:25
1

The difference between your two DoWork() methods is that the first one (that uses TaskEx.RunEx()) is not asynchronous at all. It executes fully synchronously, starts the other task on another thread, and immediately returns a completed Task. If you awaited or Wait()ed on that task, it wouldn't wait until the internal task is completed.

svick
  • 236,525
  • 50
  • 385
  • 514
0

Task.Run spawns a new thread in most scenarios as I understand it.

It's important to note that simply because you mark a method as async, and use awaiters, this does NOT (necessarily) mean that new threads are being created, completions are scheduled on the SAME thread of execution that they were called from in many cases.

The trick here has to do with the SchedulingContext. If it's set for a multithreaded apartment, then you're going to delegate completions to viable threads on the threadpool. If you're in a singlethreaded apartment as all WPF and WinForms UI code is, then it will return to the calling thread for completion allowing work to be done directly on the UI without visible thread marshalling in the code.

Firoso
  • 6,647
  • 10
  • 45
  • 91
  • `Task.Run` queues to the thread pool, which usually does not start a new thread. `await` schedules its continuations to its current `SynchronizationContext` or `TaskScheduler` (not thread) - see the end of [this article](http://msdn.microsoft.com/en-us/magazine/gg598924.aspx) which still applies as of the CTP v3 (and VS11 dev preview). I also cover async context in [my async intro post](http://nitoprograms.blogspot.com/2012/02/async-and-await.html). This context doesn't have anything to do with MTA or STA, though it's possible to have a context that supports STA or MTA. – Stephen Cleary Feb 18 '12 at 00:35