2

Assume I have a library as follows for both non-UI based apps (ASP.NET) and UI based apps (WinForm and WPF). Unfortunately I cannot avoid mixing IO-bound work and CPU-bound work but I let the consumers invoke DummyWorkAsync via Task.Run or not based on their application types (non-UI or UI based apps).

class DummyService
{
  public static async Task<int> DummyWorkAsync()
  {
    // Do some I/O first.
    await Task.Delay(1000);

    // Tons of work to do in here!
    for (int i = 0; i != 10000000; ++i)
      ;

    // Possibly some more I/O here.
    await Task.Delay(1000);

    // More work.
    for (int i = 0; i != 10000000; ++i)
      ;

    return 0;
  }
}

This allows UI-based consumer to properly use Task.Run to call the service, while ASP.NET clients would just call the method directly as follows.

private async void MyButton_Click(object sender, EventArgs e)
{
  await Task.Run(() => DummyService.DummyWorkAsync());
}

public class Home: Controller
{
  public async Task<ActionResult> IndexAsync()
  {
    var result = await DummyService.DummyWorkAsync();
    return View(result);
  }
}

Question

I am interested in the UI-based apps. Is there any difference if I use

private async void MyButton_Click(object sender, EventArgs e)
{
  await Task.Run(async () => await DummyService.DummyWorkAsync());
}

rather than

private async void MyButton_Click(object sender, EventArgs e)
{
  await Task.Run(() => DummyService.DummyWorkAsync());
}

?

Second Person Shooter
  • 14,188
  • 21
  • 90
  • 165
  • The first one would only be useful if you need to do something after `DummyWorkAsync` finishes on the UI thread, so technically it's actually waiting for it to finish but not blocking the UI thread in the mean time. – juharr Nov 20 '17 at 18:35
  • @Clemens: Because there are some CPU-bound works in the "hybrid" `DummyWorkAsync`. We need to use `Task.Run` in UI-based apps for such a hybrid asynchronous method. – Second Person Shooter Nov 20 '17 at 18:50
  • 1
    Since your IO goes first (I mean first await Task.Delay) why not just add ConfigureAwait(false) to it? Then you can call it from UI too, without wrapping in Task.Run. – Evk Nov 20 '17 at 19:21

1 Answers1

3

No, there is no (significant) difference. You are basically asking what's the difference between

static Task<int> Something() {
    return SomethingAsync();
}

and

static async Task<int> Something() {
    return await SomethingAsync();
}

Except in your case there is one more wrapping task (Task.Run), so differences in exceptions propagation for example which you may find in another questions on the same issue are not relevant in this case.

Because of that - just use

await Task.Run(() => DummyService.DummyWorkAsync());

That said, reasons for doing that are not that clear. Since your IO goes first - you can use ConfigureAwait(false) to prevent returning control to current synchronization context (which is good practice for libraries anyway). In that case, after your first await the rest (heavy CPU work) will be executed on thread pool thread (with default scheduler at least), so you won't need to use Task.Run in the first place:

public static async Task<int> DummyWorkAsync()
{
    // Do some I/O first.
    await Task.Delay(1000).ConfigureAwait(false);

    // Tons of work to do in here!
    // no problem, we are not on UI thread anyway
    for (int i = 0; i != 10000000; ++i)
        ;

    // Possibly some more I/O here.
    await Task.Delay(1000).ConfigureAwait(false);

    // More work.
    for (int i = 0; i != 10000000; ++i)
       ;

    return 0;
}
Evk
  • 98,527
  • 8
  • 141
  • 191