1

** 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.

NibblyPig
  • 51,118
  • 72
  • 200
  • 356
  • Not really, unfortunately. They say `An async method returns to the caller as soon as the first await is hit` but that is only if it's awaited, isn't it? If you don't await it, it will just return a Task and continue running. – NibblyPig Apr 16 '20 at 14:17
  • 1
    @PavelAnikhouski That example isn't quite the same since it's comparing using `await` with `.Result`. – Gabriel Luci Apr 16 '20 at 14:32
  • 1
    This question is really a duplicate of [this](https://stackoverflow.com/questions/19098143/what-is-the-purpose-of-return-await-in-c). The anonymous methods just obscure that. It's really just a difference between returning a `Task` directly, or `return await`. If you write this out with full methods (not anonymous methods) that will become clear. – Gabriel Luci Apr 16 '20 at 14:34
  • You say `ProcessCompleted` is working okay which is surprising since you seem to be created `Task` objects which is usually a bad way to end up. It'd help if we had a small *complete* sample we could run and observe for ourselves. – Damien_The_Unbeliever Apr 16 '20 at 14:35
  • Adding async-await in the `Task.Run` call is redundant because the anonymous function contains a single statement. You would need async-await if you had at least one more statement, and you wanted the second statement to be executed after the asynchronous completion of the first. – Theodor Zoulias Apr 16 '20 at 14:40
  • @GabrielLuci How so? I am not returning anything am I? – NibblyPig Apr 16 '20 at 14:43
  • @Damien_The_Unbeliever the `Task.Run` method unwraps the `Task` objects. This is one of its [differences](https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/) from `Task.Factory.StartNew`. – Theodor Zoulias Apr 16 '20 at 14:44
  • My question hasn't been answered by any of the links provided. There's no yes/no answer and no explanation. I've put in a vote to reopen it. If there is a suggestion to word the question better, I am all ears. All of the relevant code is in the question, the code that drives this is a huge MSMQ bus, it would take several pages plus installation & configuration of MSMQ to build a complete working example. – NibblyPig Apr 16 '20 at 14:44
  • Ok, it is different now that I give it more thought. I'll write up an answer. – Gabriel Luci Apr 16 '20 at 14:52
  • IMHO this question is an identical duplicate of [What is the purpose of “return await” in C#?](https://stackoverflow.com/questions/19098143/what-is-the-purpose-of-return-await-in-c). Stephen Cleary's [answer](https://stackoverflow.com/a/19098209/11178549) links to [an article](https://blog.stephencleary.com/2016/12/eliding-async-await.html) that goes in detail about the pros and cons of each approach. – Theodor Zoulias Apr 16 '20 at 14:55
  • @TheodorZoulias There is no return statement anywhere in my example, so I don't understand why that's a duplicate. I think the question is basically, task.run a method to run it on a separate thread, all good. If that method is `async Task`, do you need to make task.run `async() => await`? – NibblyPig Apr 16 '20 at 14:57
  • The [lambda operator](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-operator) (`=>`) creates a shorthand version of a function that returns something. So there is an implicit `return`. In this case the return value is a `Task`. Do you think that the syntax difference justifies the existence of an otherwise identical question? – Theodor Zoulias Apr 16 '20 at 15:07
  • I thought `() => something()` means it is `void`, and thus doesn't return anything. Task.Run takes an [Action](https://learn.microsoft.com/en-us/dotnet/api/system.action-1?view=netframework-4.8) which `Encapsulates a method that has a single parameter and does not return a value`. – NibblyPig Apr 16 '20 at 15:15
  • 1
    `Task.Run` can [also accept a `Func`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=netframework-4.8#System_Threading_Tasks_Task_Run_System_Func_System_Threading_Tasks_Task__). Which overload is being used is inferred from your method. If `something()` returns a `Task`, then the `Func` overload is used. But the fact that there is a specific overload for a method returning a `Task` makes me wonder if my answer is actually right :) I want to test this.... – Gabriel Luci Apr 16 '20 at 15:23
  • I see. So I wonder how `Task.Run(async() => await DoSomething())` compares to `Task.Run(DoSomething)`. Probably the same, right? Although actually you can't do that since the method must be marked `async` to await things inside it, which means it doesn't return a Task anymore, it's just void. So I guess it doesn't matter, and you have to do `async()=>await` – NibblyPig Apr 16 '20 at 15:29
  • If you have time for reading, here is an article that may help with understanding better the async-await technology: [Asynchronous programming with async and await](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/) *"That's the goal of this syntax: enable code that reads like a sequence of statements, but executes in a much more complicated order based on when tasks complete."* – Theodor Zoulias Apr 16 '20 at 15:38
  • Turns out I was wrong. `Task.Run(Func)` takes care of things for you. I've updated my answer. – Gabriel Luci Apr 16 '20 at 15:38
  • *"I wonder how Task.Run(async() => await DoSomething()) compares to Task.Run(DoSomething). Probably the same, right?"* - not exactly the same. If `DoSomething()` already returns a `Task`, then using `async () => await DoSomething())` just adds another method call to the stack and another `Task` in the chain. – Gabriel Luci Apr 16 '20 at 15:39
  • @GabrielLuci I commented on the answer too, but I don't think `Task.Run(DoSomething)` compiles if `DoSomething` is marked as async, because `async Task` means it returns void and that doesn't an overload of `Task.Run`. If it's not async, then you can't await other methods inside it which is of no use. – NibblyPig Apr 16 '20 at 15:47
  • `Task.Run` won't care if `DoSomething` is marked as `async`. `Task.Run` can only act on the return type, and `async` doesn't change the return type - it just enforces that the return type is `Task`. But you can return a `Task` without being `async`, which is what `() => Process()` does. – Gabriel Luci Apr 16 '20 at 15:58
  • Since you're not passing any parameters to `Process`, you can actually simplify it even more by using `Task.Run(Process)`. It takes one unneeded call out of the call stack. – Gabriel Luci Apr 16 '20 at 16:00

1 Answers1

3

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.

Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
  • That makes sense. I wonder why my code works then. Possibly the `ContinueWith` has something to do with it. I've hammered my code with over a hundred thousand queue items over a 4 hour period and it hasn't produced any errors or issues. I'll update it to use async await though, then I can slot my timer code in to measure how long it takes to execute. Thank you very much. I always struggle with async await. – NibblyPig Apr 16 '20 at 15:13
  • It all depends on how `Process()` is written. It's possible that it has already done most of the work you need it to do before the first `await` and that's why you've never seen anything go wrong. – Gabriel Luci Apr 16 '20 at 15:15
  • Oh boy I think you could be right. It removes the task from the queue, but that doesn't mean the task isn't still running. Thank you so much for this! – NibblyPig Apr 16 '20 at 15:18
  • Oh, but my code doesn't return a `Task` does it? `() => Process()` is `async Task` and therefore has a return type of `void`. Hence `Task.Run(() => Process()); ` instead of just `Task.Run(Process)`, the latter won't work if Process is marked as `async` – NibblyPig Apr 16 '20 at 15:39
  • Nothing `async` can return `void`. It always returns a `Task`. But `() => Process()` is not `async Task`, it is just `Task`. – Gabriel Luci Apr 16 '20 at 15:54
  • 1
    Intellisense can help you here. Put your mouse over the `Run` in `Task.Run(() => Process())` and it will show you which overload it's using. It will show you that it's using the `Func` overload. – Gabriel Luci Apr 16 '20 at 15:54