【问题标题】:Problems with scheduling tasks from a UI .continuewith task从 UI 调度任务的问题 .continuewith 任务
【发布时间】:2012-12-20 21:07:19
【问题描述】:

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

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

【问题讨论】:

    标签: c# task


    【解决方案1】:

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

    一些一般性的东西

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

    这是一个代码示例:

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

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

    获得正确的重载

    StartNew&lt;T&gt; 有很多重载。在下面的代码中,您选择了错误的代码,这会导致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 窗体项目中,此代码按预期工作(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++;
    }
    

    【讨论】:

    • 我以前被这个烫伤过。任务继承 TaskScheduler。
    • 我明白你在说什么,我明白传播初始 TaskScheduler 背后的原因。即使添加了 TaskScheduler.Default 参数,我仍然遇到同样的问题。 Task.Factory.StartNew((a) => WorkTask2(), TaskScheduler.Default, TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent) .ContinueWith(antecedent => WorkCompletedTask2(antecedent.Result), TaskScheduler.FromCurrentSynchronizationContext());
    • 这很奇怪。我在一个空项目中添加了一些我尝试过(并且有效)的示例代码。您是否发现了差异,或者您可以发布代码的更多详细信息吗?
    • @user1912926 相当棘手。您选择了错误的重载并将您的 TaskScheduler 视为状态。请参阅我对帖子所做的获取正确的重载编辑。 Visual Studio 中的 Ctrl+Shift+Space 会打开带有方法参数的工具提示 - 如果您再次遇到此类问题,可能会对您有所帮助。
    • @user1912926 很高兴为您提供帮助。如果它解决了你的问题,你能接受答案吗? :)
    猜你喜欢
    • 2018-02-02
    • 2021-11-30
    • 2011-11-02
    • 1970-01-01
    • 1970-01-01
    • 2016-03-04
    • 1970-01-01
    • 1970-01-01
    • 2013-06-28
    相关资源
    最近更新 更多