How to force Task.Factory.StartNew to a background thread?
In order to force the launch of a new thread, you should specify TaskScheduler.Default in the call to Task.Factory.StartNew as follows:
Task.Factory.StartNew(action,
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default).ContinueWith(completeAction);
In my testing you do not need to specify TaskCreationOptions.LongRunning in order to force a background thread, though it shouldn't hurt.
Related videos on Youtube
Trevor Elliott
Updated on September 16, 2022Comments
-
Trevor Elliott over 1 year
I have seen numerous other questions similar to this but did not find my answer there.
My problem was that I was creating threads with the following flow:
private void btn_Click(object sender, EventArgs e) { service.GetCount( (count, ex) => { if (ex != null) return; for (int i = 0; i < count; i++) { service.Get(onItemReceived, i); } } ); } public void GetCount(Action<int, Exception> callback) { var callingThread = TaskScheduler.FromCurrentSynchronizationContext(); Func<int> action = () => { return client.GetCount(); // Synchronous method, could take a long time }; Action<Task<int>> completeAction = (task) => { Exception ex = (task.Exception != null) ? task.Exception.InnerException : task.Exception; if (callback != null) callback(task.Result, ex); }; Task.Factory.StartNew(action).ContinueWith(completeAction, callingThread); } public void Get(Action<object, Exception> callback, int index) { var callingThread = TaskScheduler.FromCurrentSynchronizationContext(); Func<object> action = () => { return client.Get(index); // Synchronous method, could take a long time }; Action<Task<object>> completeAction = (task) => { Exception ex = (task.Exception != null) ? task.Exception.InnerException : task.Exception; if (callback != null) callback(task.Result, ex); }; Task.Factory.StartNew(action).ContinueWith(completeAction, callingThread); }
In this way, each of my service's asynchronous methods would callback the thread they were originally called on (generally the UI thread). So I am simulating how the await/async keywords work (I cannot use .NET 4.5).
The problem with this pattern is that I get inexplicably locked to the UI thread after the first call to "ContinueWith". So in this case if I attempt to spawn 5 threads to each process a synchronous function Get, they will execute 1 by 1 instead of in parallel and they will block the UI thread while doing so, even if I try specifying TaskCreationOptions.LongRunning.
This never happens with my first call to Task.Factory.StartNew, it only happens to subsequent calls from within the first callback.
-
Servy almost 11 years
LongRunning
forces the creation of an entirely new non-thread pool thread. -
Henk Holterman almost 11 years@Servy - it doesn't force anything, it's a hint for the scheduler.
-
Servy almost 11 years@HenkHolterman In theory, yes, that's correct. In practice, in the current implementation, it results in a new thread being created. While I agree it might be best to assume it's possible that it won't, you should assume that marking a task as long running when it's really not will consume additional resources.
-
Trevor Elliott almost 11 yearsIn my scenario listed in the question above, TaskCreationOptions.LongRunning does NOT force a new background thread. Only using TaskScheduler.Default did.
-
rwong about 9 years@Servy The reason for raising this issue is that if anyone depends on this implementation detail (that the following task will execute on a different thread then the current one), it is possible to cause a deadlock (aptly known as the "self-deadlock" or "self-waiting deadlock") if the assumption is violated.
-
Servy about 9 years@rwong And the fact that changing the implementation of this method would cause such a deadlock is exactly why MS wouldn't make such a change.