4

I saw some strange code following a pattern like this below in our production code base. I was wondering what the performance impact is when Start() is called vs if I just ran the synchronous code directly. Especially on how the task or threads managed in this case.

public Task RunLongTask(){
    return Task.Run(()=>{
       // Run some Synchronous long IO process
    }
}

public void Start(){
    RunLongTask().Wait();
}

Is my thinking correct that one task will be blocked while waiting for the synchronous IO call to finish. And the caller on RunLongTask() will also be blocked. Essentially doubling the cost of running the synchronous code directly. Also would 2 threads always be created in this case due to the double blocking?

roverred
  • 1,841
  • 5
  • 29
  • 46
  • do you have `IO` or any thread else where that calling this `RunLongTask`? – prhmma Nov 30 '19 at 09:46
  • What kind of application do you have? Is it a asp.net or desktop or something else? – Pavel Anikhouski Nov 30 '19 at 09:47
  • Does this answer your question? [Use Task.Run() in synchronous method to avoid deadlock waiting on async method?](https://stackoverflow.com/questions/28305968/use-task-run-in-synchronous-method-to-avoid-deadlock-waiting-on-async-method) – Adrian Iftode Nov 30 '19 at 09:50
  • @prhmma I've seen it called from the same and another thread. It looked like the user was trying to force the call to be synchronous since Task.Run is called automatically. – roverred Nov 30 '19 at 09:58
  • @PavelAnikhouski it's asp.net. Does that change the task management? – roverred Nov 30 '19 at 09:58
  • @AdrianIftode that question looks different because in this case the method ran in the task is not asynchronous. So I don't believe a deadlock is possible? But I'm more interested on the performance impact. – roverred Nov 30 '19 at 10:01
  • It basically starts a thread of `RunLongTask`, and create as many threads as it calls, and they wait asynchronously to receive the desired `IO`. caller of this `RunLongTask` will not be blocked unless the rest of the function depends on what these threads return. – prhmma Nov 30 '19 at 10:02
  • @prhmma that's interesting to know. Do you mean if the Task from `RunLongTask` returned some value that had to be used in the caller? But I'm also looking at this blog https://devblogs.microsoft.com/pfxteam/task-wait-and-inlining/ and it claims if a task has started already, it will become blocking, otherwise it may be async. So would it not be blocking in my case since I'm returning a started task. – roverred Nov 30 '19 at 10:22
  • @roverred Please, have a look at updated answer – Pavel Anikhouski Nov 30 '19 at 13:54
  • @roverred you are using asp.net core or legacy asp.net? – Farhad Jabiyev Nov 30 '19 at 18:30
  • @FarhadJabiyev legacy. But I'd be interested to know how it'd be different between the two. – roverred Dec 01 '19 at 00:42
  • 1
    @roverred in asp.net core there is no any SynchronizationContext -> https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html – Farhad Jabiyev Dec 01 '19 at 15:20

1 Answers1

2

Task.Run method queues the specified work to run on the ThreadPool, according to MSDN. The Task.Wait method is blocking one. The simple explanation of this can be found in this article

Even if the underlying task is asynchronous, if you call a blocking method or blocking property on the task, execution will wait for the task to complete - but will do so synchronously, such that the current thread is completely occupied during the wait. So, if you use one of the above properties/methods, be sure that's actually what you meant to do.

So, you current thread (which executes RunLongTask().Wait();) will be blocked until work inside this Task is completed synchronously

Task.Run(()=>{
       // Run some Synchronous long IO process
    }

There is no doubling of running the synchronous code directly, your main thread is just blocked and waiting until I/O process is completed on a separate thread

Also would 2 threads always be created in this case due to the double blocking?

You already have at least one Thread, the current one, which is calling Task.Run. Task.Run will obtain a free worker thread from thread pool (if any of them), but it will be a different thread.

In terms of performance running the I/O operation on a separate thread isn't very efficient (as it can be for CPU bound work), because the worker thread will mostly be waiting for sending/receiving the data. It's better to make your long IO process asynchronous and run it without blocking (if it supports asynchronous APIs, of course).

Another potential performance drawback is that ASP.NET Core uses one thread per request and running a background thread with long running IO process will reduce the number of threads available to handle new requests (and scalability of your application as well)

Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66