从 UI .continuewith 任务调度任务的问题 [英] Problems with scheduling tasks from a UI .continuewith task

查看:34
本文介绍了从 UI .continuewith 任务调度任务的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

My application schedules a long running task using the following code:

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

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

WorkCompletedTask1 is scheduled and displays results on the UI as expected. Depending on results from WorkTask1, WorkCompletedTask1 may schedule additional tasks using the following statement:

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

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

WorkTask2 does NOT run on a separate thread as expected; it runs on the UI thread which is blocked until WorkTask2 completes. I thought the TaskCreationOptions.LongRunning would guarantee a separate thread.

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

Any suggestions on why this does not work? I can schedule addition tasks from UI and non-UI tasks, just not from a .continuewith task in the UI.

损坏的示例项目代码

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

In an empty Windows Forms project with button1 button on the form, this code does NOT work as expected (Windows 7 VS2010 Express Net 4.0). T2 and T3 run in the UI thread, not a worker thread. Add a listBox1 to your button1 form and try the following:

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

推荐答案

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

In .NET 4.0 you have to pass TaskScheduler.Default explicitly. You picked the wrong overload to to this (see below).

一些通用的东西

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

In a continuation on the UI thread the TaskScheduler is still UI thread returned by FromCurrentSynchronizationContext method. Therefore, all new Tasks you start are scheduled on the UI thread too unless you pass a TaskScheduler explicitly:

这是一个代码示例:

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

随意使用任何你需要的 TaskScheduler,但你需要明确说明.

Feel free to use whatever TaskScheduler you need, but you need to state it explicitly.

获得正确的过载

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

There are quite a few overloads for StartNew<T>. In your code below you pick the wrong one, which causes TaskScheduler.Default to act as state (a value passed as a to T3) rather than the actual scheduler:

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

显然,这样你不会得到你想要的日程安排,而是上面描述的默认行为.

Obviously this way you won't get the scheduling you want, but the default behaviour described above.

.NET 4.5 的附加信息

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

In .NET 4.5, there is TaskContinuationOptions.HideScheduler to change this behavior. See New TaskCreationOptions and TaskContinuationOptions in .NET 4.5 by Stephen Toub for more details on the new options and let me quote a code sample out of it:

// 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):

In an empty Windows Forms project with button1 button on the form, this code works as expected (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++;
}

这篇关于从 UI .continuewith 任务调度任务的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆