NibblyPig
NibblyPig

Reputation: 52952

Should I be using await inside my Task.Run()?

** I've summarised this question at the bottom with an edit **

This has been asked before but I think my circumstances are different.

I am processing multiple requests simultaneously.

This is my code to do that, it runs in a loop. I've removed a bit of code that handles the taskAllocated variable for brevity.

 while (!taskAllocated)
 {
     lock (_lock)
     {
         // Find an empty slot in the task queue to insert this task
         for (i = 0; i < MaxNumTasks; i++)
         {
             if (_taskQueue[i] == null)
             {
                 _taskQueue[i] = Task.Run(() => Process());
                 _taskQueue[i].ContinueWith(ProcessCompleted);
                 break;
             }
         }
     }
  }

Process is a typical async Task Process() { CpuIntensiveStuff(); } method.

I've been running the above code, and it has been working fine. It multithreads nicely. Whenever an item comes in, it will find an empty slot in the task queue, and kick it off. When the task completes, the ProcessCompleted method runs, and frees up the slot.

But then I thought, shouldn't I be using await inside my Task.Run? Something like:

_taskQueue[i] = Task.Run(async () => await Process());

After thinking about it, I'm not sure. ContinueWith triggers correctly, when the task has completed, so perhaps it's not necessary.

I ask because I wanted to monitor and log how long each task takes to complete.

So Instead of Process(), I would make another method like:

async Task DoProcess() 
{ 
    var sw = Stopwatch.StartNew();
    Process();
    sw.Stop();
    Log(sw.ElapsedMilliseconds);
}

And it occurred to me that if I did that, I wasn't sure if I'd need to await Process(); or not, in addition to not knowing if I should await inside the Task.Run()

I'm in a bit of a tizz about this. Can anyone offer guidance?


Edit:

To summarise:

If Somemethod is:

void SomeMethod() { }

Then

Task.Run(() => SomeMethod()); is great, calls SomeMethod on a new 'thread' (not technically, but you know what I mean).

However, my SomeMethod is actually:

async Task SomeMethod() { }

Do you need to do anything special with Task.Run()?

My code, I am not, I am just straight up ignoring that it's an async Task, and that seems to work:

Task.Run(() => SomeMethod()); // SomeMethod is async Task but I am ignoring that

But I'm not convinced that it a) should work or b) is a good idea. The alternative could be to do:

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

But is there any point? And this is compounded by the fact I want to really do:

Task.Run(() => 
{ 
    someCode(); 
    var x = startTimer();
    SomeMethod(); 
    var y = stopTimer();
    someMoreCode()
}); 

but without await I'm not sure it will wait for somemethod to finish and the timer will be wrong.

Upvotes: 1

Views: 114

Answers (1)

Gabriel Luci
Gabriel Luci

Reputation: 41008

Things become more clear if you do not use anonymous methods. For example,

Task.Run(() => Process())

is equivalent to this:

Task.Run(DoSomething);

Task DoSomething() {
    return Process();
}

Whereas

Task.Run(async () => await Process())

is equivalent to this:

Task.Run(DoSomething);

async Task DoSomething() {
    await Process();
}

In most cases, there is no functional difference between return SomethingThatReturnsATask() and return await SomethingThatReturnsATask(), and you usually want to return the Task directly and not use await (for reasons described here). When used inside Task.Run, things could easily go bad if the .NET team didn't have your back.

It is important to note that asynchronous methods start running on the same thread just like any other method. The magic happens at the first await that acts on an incomplete Task. At that point, await returns its own incomplete Task. That's important - it returns, with a promise to do the rest later.

This could have meant that the Task returned from Task.Run would complete whenever Process() returns a Task. And since Process() returns a Task at the first await, that would happen when it has not yet totally completed.

The .NET team has your back

That is not the case however, because Task.Run has a specific overload for when you give it a method returning a Task. And if you look at the code, it returns a Task *that is tied to the Task you return.

That means that the Task returned from Task.Run(() => Process()) will not complete until the Task returned from Process() has completed.

So your code is fine the way it is.

Upvotes: 3

Related Questions