Can the TPL run the Task on more than one thread?

2019-06-17 02:22发布

Mono/Xamarin-specific answers are welcomed.

I'm running System.Threading.Tasks using Task.Run(). Will The TPL assign the created task to a single thread for the life of the task's execution? Or is it possible that the created task will get preempted while running, and then be scheduled again on a different thread?

Will Thread.CurrentThread.ManagedThreadId be constant over the life of a Task?

Is the answer different for long-running tasks?

Is there a way to control TPL behavior in this regard?

2条回答
家丑人穷心不美
2楼-- · 2019-06-17 02:48

Will The TPL assign the created task to a single thread for the life of the task's execution?

The guideline is that synchronous portions of work run on a single thread. So, if you pass a synchronous delegate to Task.Run, it will all run on a single thread:

await Task.Run(() => Thread.Sleep(5000)); // same thread after sleep

However, if you have asynchronous code, every await is a location in the code where the method is suspended. When the method resumes, it will resume on a thread pool thread (which may be a different thread).

await Task.Run(async () => await Task.Delay(5000)); // thread may change

The long-running flag (which cannot be passed to Task.Run) does not affect this behavior because it only applies to the first synchronous portion.

The normal way to control this is by using a custom TaskScheduler or SynchronizationContext. However, before going down that road, you may want to consider an alternative approach. There should be a better solution than forcing the method back onto the same thread. For example, thread-affine synchronization primitives all have async-compatible equivalents, thread-local storage can be replaced by closures / class fields / logical call context, etc.

查看更多
一纸荒年 Trace。
3楼-- · 2019-06-17 03:01

Task.Run will schedule the delegate to be executed in the thread pool, and the thread pool will only ever execute the given delegate on a single thread. It won't move it around between threads.

That said, a Task conceptually is merely the representation of the completion of some asynchronous operation at some point in the future. It can represent anything that finishes eventually. If you want to create a task that represents the execution of one delegate on one thread, then the execution of another delegate on a different thread, then you absolutely can do that.

Many methods using the async keyword will in fact do that. Since, in an application that doesn't have a synchronization context set, the continuations wired up as a result of await calls may (they can sometimes be optimized out) each be scheduled separately in the thread pool. For example, the following code run in a console app generates a task that could potentially print out three entirely different numbers. (Of course, they aren't required to be different; the thread pool could just happen to schedule the continuations to run on the same thread.)

public static async Task Foo()
{
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    await Task.Delay(100);
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    await Task.Delay(100);
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}
查看更多
登录 后发表回答