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は、期待どおりに別のスレッドで実行されません。WorkTask2が完了するまでブロックされるUIスレッドで実行されます。TaskCreationOptions.LongRunningは別のスレッドを保証すると思いました。

これが機能しない理由について何か提案はありますか?UIの.continuewithタスクからではなく、UIタスクと非UIタスクから追加タスクをスケジュールできます。

壊れたサンプルプロジェクトコード

フォームにボタンがある空のWindowsフォームプロジェクトでbutton1は、このコードは期待どおりに機能しません(Windows 7 VS2010 Express Net4.0)。T2とT3は、ワーカースレッドではなく、UIスレッドで実行されます。button1フォームにlistBox1を追加して、次のことを試してください。

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スレッドの続きでは、メソッドによって返されるTaskSchedulerUIスレッドです。FromCurrentSynchronizationContextしたがって、明示的に渡さTasksない限り、開始するすべての新規はUIスレッドでもスケジュールされます。TaskScheduler

コードサンプルは次のとおりです。

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

必要なものは何でも自由に使用TaskSchedulerできますが、明示的に述べる必要があります。

適切な過負荷を取得する

にはかなりの数のオーバーロードがありStartNew<T>ます。以下のコードでは、間違ったものを選択しています。これにより、実際のスケジューラではなく(として渡された値)としてTaskScheduler.Default機能します。stateaT3

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

明らかに、この方法では必要なスケジュールを取得できませんが、上記のデフォルトの動作です。

.NET4.5の追加情報

.NET 4.5では、TaskContinuationOptions.HideSchedulerこの動作を変更する必要があります。新しいオプションの詳細については、StephenToubによる.NET4.5の新しい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()); 

作業サンプルプロジェクトコード

フォームにボタンがある空のWindowsフォームプロジェクトでbutton1は、このコードは期待どおりに機能します(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 に答える