从UI调度任务的问题。继续任务

我的应用程序使用以下代码安排长时间运行的任务:

Task.Factory.StartNew((a) => WorkTask1(), TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent) .ContinueWith(antecedent => WorkCompletedTask1(antecedent.Result), TaskScheduler.FromCurrentSynchronizationContext()); 

计划WorkCompletedTask1并按预期在UI上显示结果。 根据WorkTask1的结果,WorkCompletedTask1可以使用以下语句安排其他任务:

 Task.Factory.StartNew((a) => WorkTask2(), TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent) .ContinueWith(antecedent => WorkCompletedTask2(antecedent.Result), TaskScheduler.FromCurrentSynchronizationContext()); 

WorkTask2不会按预期在单独的线程上运行; 它在UI线程上运行,该线程被阻塞直到WorkTask2完成。 我以为TaskCreationOptions.LongRunning会保证一个单独的线程。

为什么这不起作用的任何建议? 我可以从UI和非UI任务安排添加任务,而不是从UI中的.continuewith任务安排。

破碎的示例项目代码

在窗体上带有button1按钮的空Windows窗体项目中,此代码无法按预期工作(Windows 7 VS2010 Express Net 4.0)。 T2和T3在UI线程中运行,而不是工作线程。 将listBox1添加到button1表单并尝试以下操作:

 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((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((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((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"); } 

在.NET 4.0中,您必须显式传递TaskScheduler.Default 。 你选择了错误的重载(见下文)。

一些一般的东西

在UI线程的延续中, TaskScheduler仍然是FromCurrentSynchronizationContext方法返回的UI线程 。 因此,除非您明确传递TaskScheduler否则您启动的所有新Tasks也将安排在UI线程上:

这是一个代码示例:

 Task.Factory.StartNew(foo => {}, TaskScheduler.Default) 

随意使用您需要的任何TaskScheduler ,但您需要明确说明它。

获得正确的超载

StartNew有很多重载。 在下面的代码中,您选择了错误的代码,这会导致TaskScheduler.Default充当state (作为传递给T3的值)而不是实际的调度程序

 var options = TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent; // overload with Func, CancellationToken, options and TaskScheduler Task.Factory.StartNew(() => T2(), new CancellationToken(), options, TaskScheduler.Default); // overload with Func 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((a) => T3(), TaskScheduler.Default, // state, not scheduler options); 

显然,这样你就不会得到你想要的调度,但是上面描述的默认行为

.NET 4.5的附加信息

在.NET 4.5中,有TaskContinuationOptions.HideScheduler来更改此行为。 有关新选项的更多详细信息,请参阅Stephen Toub在.NET 4.5中的新TaskCreationOptions和TaskContinuationOptions,并让我引用其中的代码示例:

 // 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()); 

工作示例项目代码

在窗体上带有button1按钮的空Windows窗体项目中,此代码按预期工作(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++; }