4

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

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

1 回答 1

8

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

一些一般的东西

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

这是一个代码示例:

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需要更改此行为。有关新选项的更多详细信息,请参阅Stephen Toub的 .NET 4.5 中的 New 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++;
}
于 2012-12-18T14:55:07.717 回答