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

时间:2012-12-18 13:39:52

标签: c# task

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

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

1 个答案:

答案 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++;
}