1

I have an ASPX page which I cannot convert to async but which uses some async methods in a synchronous context. The way it invokes them is like so:

public void MySyncMethod()
{
    var myTask = Task.Run(() => _myField.DoSomethingAsync());
    myTask.Wait();
    //use myTask.Result
}

Is there any difference between doing that and the following as far as async/await and/or blocking goes?

public void MySyncMethod()
{
    var myTask = _myField.DoSomethingAsync(); //just get the Task direct, no Task.Run
    myTask.Wait();
    //use myTask.Result
}

I assume a previous developer added the Task.Run for a reason. But I am having issues which accessing things in HttpContext as the work is being run on a different thread.

Is there a reason to use Task.Run here?

thisextendsthat
  • 1,266
  • 1
  • 9
  • 26
  • Adding your task into Task.Run is not needed if it's also async. Task.Run is used to run synchronous code asynchronously. the bottom one is the best way to go about it imho. – Sasha Aug 17 '18 at 09:45
  • Where possible, if you can't make your code itself async (and you should try really hard to), use sync APIs if the system offers both sync and async. You don't *benefit* from async here where you block your request thread anyway. – Damien_The_Unbeliever Aug 17 '18 at 09:47

3 Answers3

5

Is there any difference between doing that and the following as far as async/await and/or blocking goes?

Yes the first block of code uses a thread pool thread then waits for this to return, so your using two threads not one. They both block.

I assume a previous developer added the Task.Run for a reason.

Yes, blocking (directly) on async code from an ASP.Net context is a bad idea and can cause deadlocks. So you second block of code is more efficent (in thread usage) but suffers from serious deadlock issues.

The correct solution here is to make public void MySyncMethod() async itself (public async Task MySyncMethod()). Both these solutions have drawbacks and the only real way out is to make the whole call stack async. If you can do this, do it.

If you can't call an async method from another async method then Task.Run is the way to go. See How to call asynchronous method from synchronous method in C#? for more details.

If you want HttpContext inside your thread have a read though Using HttpContext in Async Task I would definitely favour:

Options of those answers and keep in mind

First off, you're not creating a copy of the object, you're just copying the reference to the object.HttpContext isn't a struct.....etc

Liam
  • 27,717
  • 28
  • 128
  • 190
  • 2
    We don't know that they both block. We know the request thread does. Without knowing what `DoSomethingAsync` does, we know nothing about that other thread. – Damien_The_Unbeliever Aug 17 '18 at 09:48
  • I'm making the assumption that whoever wrote `DoSomethingAsync` made it `async` for a reason, but your right. – Liam Aug 17 '18 at 09:51
  • 1
    yes, they did. And generally, the whole point of async is to *not* block threads. See e.g. Steven Cleary's excellent [there is no thread](https://blog.stephencleary.com/2013/11/there-is-no-thread.html) – Damien_The_Unbeliever Aug 17 '18 at 09:52
  • 1
    Or Eric Lippert's introductory tour to async from 2010 - [part two](https://blogs.msdn.microsoft.com/ericlippert/2010/10/29/asynchronous-programming-in-c-5-0-part-two-whence-await/): " The “await” operator used twice in that method does **not** mean “*this method now blocks the current thread until the asynchronous operation returns*”. That would be making the asynchronous operation back into a synchronous operation, which is precisely what we are attempting to avoid. Rather, it means the **opposite** of that;" – Damien_The_Unbeliever Aug 17 '18 at 09:55
  • 1
    I've been in this conundrum myself, you've picked up some legacy code, you want to implement *x* that's written all `async`, the `async` code is buried in the world before async, with a heavy heart, you reach for the `Task.Run` – Liam Aug 17 '18 at 09:55
  • @Liam if blocking in the ASP.Net context is a no-no but the work being done in the `Task.Run` relies on `HttpContext.Current` (which won't be available on the other thread) then is the only solution to pass whatever is needed into the method? I.e. pass the context, or whatever I need from the context. I can't change the outer method to `async`, it must remain `void`. – thisextendsthat Aug 17 '18 at 10:00
  • 1
    See [ASP.NET HttpContext.Current inside Task.Run](https://stackoverflow.com/questions/39704409/asp-net-httpcontext-current-inside-task-run) @thisextendsthat. To re-iterate, **the best solution** is all `async` or no `async`. Everything else is a compromise at best. – Liam Aug 17 '18 at 10:02
  • Here's another on the subject [Using HttpContext in Async Task](https://stackoverflow.com/questions/13748267/using-httpcontext-in-async-task) – Liam Aug 17 '18 at 10:05
0

The internal workings of asynchronous code based on async/await is fundamentally different than tasks started by Task.Run. async/await tasks are promise based and depend on the caller cooperating with returning the execution back to the asynchronous method when appropriate. Tasks started by Task.Run however are usually started on a parallel thread taken from the thread pool and do not depend on the caller's cooperation to continue execution when appropriate.

This constellation leads to the problem that you can not treat a promise based task the same as the other tasks, since the promise based task might wait for the callers cooperation to return the execution, which might never occur since the other task is executed independently and might wait for the caller. The result is a deadlock.

The solution is a specific Task.Run overload that will create a proxy for an existing task-based method that allows proper execution of a promise based task. It is safe to call Wait on this proxy. That's why the other developer used this construct. He could have also simplified the call and avoided an anonymous method like this:

var myTask = Task.Run(_myField.DoSomethingAsync);
Sefe
  • 13,731
  • 5
  • 42
  • 55
0

Task.Run is used to run code asynchronously. Be clear that it returns Task and needs to be awaited. Here's an example:

Task myTask = Task.Run(() => DoSomething());
await myTask;
SNBS
  • 671
  • 2
  • 22