我的应用程序使用以下代码安排长时间运行的任务:
Task.Factory.StartNew<bool>((a) => WorkTask1(),
TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent)
.ContinueWith(antecedent => WorkCompletedTask1(antecedent.Result),
TaskScheduler.FromCurrentSynchronizationContext());
计划WorkCompletedTask1并按预期在UI上显示结果。根据WorkTask1的结果,WorkCompletedTask1可以使用以下语句安排其他任务:
Task.Factory.StartNew<bool>((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<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");
}
答案 0 :(得分:8)
在.NET 4.0中,您必须明确传递TaskScheduler.Default
。你选择了错误的重载(见下文)。
一些常规资料
在UI线程的延续中,TaskScheduler
仍然是FromCurrentSynchronizationContext
方法返回的 UI线程。因此,除非您明确传递Tasks
,否则您启动的所有新TaskScheduler
都会在UI线程上安排:
以下是代码示例:
Task.Factory.StartNew(foo => {}, TaskScheduler.Default)
随意使用您需要的任何TaskScheduler
,但您需要明确说明。
获得正确的超载
StartNew<T>
有很多重载。在下面的代码中,您选择了错误的代码,这会导致TaskScheduler.Default
充当state
(值a
传递给T3
)而不是实际调度程序:
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);
显然,这样你就不会得到你想要的日程安排,而是上面描述的默认行为。
.NET 4.5的附加信息
在.NET 4.5中,有TaskContinuationOptions.HideScheduler
来更改此行为。有关新选项的更多详细信息,请参阅New TaskCreationOptions and TaskContinuationOptions in .NET 4.5 by Stephen Toub,让我引用代码示例:
// 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 Forms项目中,此代码按预期工作(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++;
}