My application schedules a long running task using the following code:
Task.Factory.StartNew<bool>((a) => WorkTask1(),
TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent)
.ContinueWith(antecedent => WorkCompletedTask1(antecedent.Result),
TaskScheduler.FromCurrentSynchronizationContext());
WorkCompletedTask1 is scheduled and displays results on the UI as expected. Depending on results from WorkTask1, WorkCompletedTask1 may schedule additional tasks using the following statement:
Task.Factory.StartNew<bool>((a) => WorkTask2(),
TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent)
.ContinueWith(antecedent => WorkCompletedTask2(antecedent.Result),
TaskScheduler.FromCurrentSynchronizationContext());
WorkTask2 does NOT run on a separate thread as expected; it runs on the UI thread which is blocked until WorkTask2 completes. I thought the TaskCreationOptions.LongRunning would guarantee a separate thread.
Any suggestions on why this does not work? I can schedule addition tasks from UI and non-UI tasks, just not from a .continuewith task in the UI.
Broken Sample Project Code
In an empty Windows Forms project with button1
button on the form, this code does NOT work as expected (Windows 7 VS2010 Express Net 4.0). T2 and T3 run in the UI thread, not a worker thread.
Add a listBox1 to your button1 form and try the following:
private delegate void DelegateSendMsg(String msg);
private DelegateSendMsg m_DelegateSendMsg;
private TaskScheduler uiSched;
private Process thisProcess;
private string
thisProcessName,
thisProcessId,
uiThreadName,
nonuiStatus = "Non-UI",
uiStatus = "UI";
private void Form1_Load(object sender, EventArgs e)
{
thisProcess = Process.GetCurrentProcess();
thisProcessName = thisProcess.ProcessName;
thisProcessId = thisProcess.Id.ToString();
uiThreadName = CurrentThread;
m_DelegateSendMsg = this.SendMsg;
uiSched = TaskScheduler.FromCurrentSynchronizationContext();
SendMsg("UI thread name is " + CurrentThread);
}
//create the name of the current task
public string CurrentThread
{
get
{
string threadId = null;
if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
threadId = thisProcess.Id.ToString() + "=" + thisProcessName;
else
threadId = thisProcessId
+ "=" + thisProcessName
+ "/" + Thread.CurrentThread.Name;
threadId += ":" + Thread.CurrentThread.ManagedThreadId + " ";
return threadId;
}
}
//validate if the function is running in the expected UI state or not
public bool MeetsUIExpectations(string functionName, string expectedStatus)
{
bool rc = true;
string currentThreadName = CurrentThread;
string text =
"Function " + functionName + " running in thread " + currentThreadName;
if ((currentThreadName == uiThreadName) & expectedStatus == uiStatus)
text += ": UI status as expected";
else if ((currentThreadName != uiThreadName) & expectedStatus == nonuiStatus)
text += ": non-UI status as expected";
else
{
text += ": UI status is NOT as expected!"
+ " Expected: " + expectedStatus
+ "; running in thread" + currentThreadName;
rc = false;
}
SendMsg(text);
return rc;
}
//display a single text message
private void SendMsg(String msg)
{
if (this.InvokeRequired)
try { this.Invoke(m_DelegateSendMsg, "UI context switch: " + msg); }
catch (Exception) { }
else
{
listBox1.Items.Add(msg);
listBox1.TopIndex = listBox1.Items.Count - 1;
}
}
private void button1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew<bool>((a) =>
T1(), TaskScheduler.Default,
TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent)
.ContinueWith(antecedent => T1Completed(antecedent.Result), uiSched);
}
private bool T1()
{
//get the name of the currently running function and validate UI status
var currentMethod = System.Reflection.MethodInfo.GetCurrentMethod();
MeetsUIExpectations(currentMethod.ToString(), nonuiStatus);
int i = 0;
while (i < Int32.MaxValue) i++;
return true;
}
private void T1Completed(bool successful)
{
var currentMethod = System.Reflection.MethodInfo.GetCurrentMethod();
MeetsUIExpectations(currentMethod.ToString(), uiStatus);
if (successful)
{
Task.Factory.StartNew<bool>((a) =>
T2(), TaskScheduler.Default,
TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent)
.ContinueWith(antecedent => T2Completed(antecedent.Result), uiSched);
}
}
private bool T2()
{
var currentMethod = System.Reflection.MethodInfo.GetCurrentMethod();
MeetsUIExpectations(currentMethod.ToString(), nonuiStatus);
int i = 0;
while (i < Int32.MaxValue) i++;
return true;
}
private void T2Completed(bool successful)
{
var currentMethod = System.Reflection.MethodInfo.GetCurrentMethod();
MeetsUIExpectations(currentMethod.ToString(), uiStatus);
Task.Factory.StartNew<bool>((a) =>
T3(), TaskScheduler.Default,
TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent)
.ContinueWith(antecedent => T3Completed(antecedent.Result), uiSched);
}
private bool T3()
{
var currentMethod = System.Reflection.MethodInfo.GetCurrentMethod();
MeetsUIExpectations(currentMethod.ToString(), nonuiStatus);
int i = 0;
while (i < Int32.MaxValue) i++;
return true;
}
private void T3Completed(bool successful)
{
//get the name of the currently running function and validate UI status
var currentMethod = System.Reflection.MethodInfo.GetCurrentMethod();
MeetsUIExpectations(currentMethod.ToString(), uiStatus);
SendMsg("All functions completed");
}
In .NET 4.0 you have to pass TaskScheduler.Default
explicitly. You picked the wrong overload to to this (see below).
Some General Stuff
In a continuation on the UI thread the TaskScheduler
is still UI thread returned by FromCurrentSynchronizationContext
method. Therefore, all new Tasks
you start are scheduled on the UI thread too unless you pass a TaskScheduler
explicitly:
Here is a code sample:
Task.Factory.StartNew(foo => {}, TaskScheduler.Default)
Feel free to use whatever TaskScheduler
you need, but you need to state it explicitly.
Get the Right Overload
There are quite a few overloads for StartNew<T>
. In your code below you pick the wrong one, which causes TaskScheduler.Default
to act as state
(a value passed as a
to T3
) rather than the actual scheduler:
var options = TaskCreationOptions.LongRunning
| TaskCreationOptions.AttachedToParent;
// overload with Func<bool>, CancellationToken, options and TaskScheduler
Task.Factory.StartNew<bool>(() => T2(), new CancellationToken(),
options, TaskScheduler.Default);
// overload with Func<object, bool> with state and options
// scheduler acts as state here instead of actual scheduler, and
// is therefore just passed as (a) to T3 (state is an object, thus
// can also be a TaskScheduler instance)
Task.Factory.StartNew<bool>((a) => T3(),
TaskScheduler.Default, // state, not scheduler
options);
Obviously this way you won't get the scheduling you want, but the default behaviour described above.
Additional Information for .NET 4.5
In .NET 4.5, there is TaskContinuationOptions.HideScheduler
to change this behavior. See New TaskCreationOptions and TaskContinuationOptions in .NET 4.5 by Stephen Toub for more details on the new options and let me quote a code sample out of it:
// code sample copied from blog post stated above
Task.Factory.StartNew(() =>
{
// #2 long-running work, so offloaded to non-UI thread
}).ContinueWith(t =>
{
// #3 back on the UI thread
Task.Factory.StartNew(() =>
{
// #4 compute-intensive work we want offloaded to non-UI thread (bug!)
});
}, CancellationToken.None,
TaskContinuationOptions.HideScheduler, // <-- new option stated in text
TaskScheduler.FromCurrentSynchronizationContext());
Working Sample Project Code
In an empty Windows Forms project with button1
button on the form, this code works as expected (Windows 7, .NET 4.0):
private void button1_Click(object sender, EventArgs e)
{
var uiSched = TaskScheduler.FromCurrentSynchronizationContext();
button1.Enabled = false;
// this HardWork-task is not blocking, as we have
// TaskScheduler.Default as the default scheduler
Task.Factory.StartNew(HardWork)
.ContinueWith(t =>
{
button1.Enabled = true;
// this HardWork-task will block, as we are on the
// UI thread scheduler
Task.Factory.StartNew(HardWork)
.ContinueWith(t2 =>
{
button1.Enabled = false;
// this one will not, as we pass TaskScheduler.Default
// explicitly
Task.Factory.StartNew(HardWork,
new CancellationToken(),
TaskCreationOptions.None,
TaskScheduler.Default).ContinueWith(t3 =>
{
button1.Enabled = true;
}, uiSched); // come back to UI thread to alter button1
}, uiSched); // come back to UI thread to alter button1
}, uiSched); // come back on UI thread to alter button1
}
public void HardWork()
{
int i = 0;
while(i < Int32.MaxValue) i++;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With