14

これに似た他の多くの質問を見てきましたが、そこに私の答えが見つかりませんでした.

私の問題は、次のフローでスレッドを作成していたことです。

private void btn_Click(object sender, EventArgs e)
{
    service.GetCount(
        (count, ex) =>
        {
            if (ex != null)
                return;

            for (int i = 0; i < count; i++)
            {
                service.Get(onItemReceived, i);
            }
        }
    );
}

public void GetCount(Action<int, Exception> callback)
{
    var callingThread = TaskScheduler.FromCurrentSynchronizationContext();

    Func<int> action = () =>
    {
        return client.GetCount(); // Synchronous method, could take a long time
    };

    Action<Task<int>> completeAction = (task) =>
    {
        Exception ex = (task.Exception != null) ? task.Exception.InnerException : task.Exception;

        if (callback != null)
            callback(task.Result, ex);
    };

    Task.Factory.StartNew(action).ContinueWith(completeAction, callingThread);
}

public void Get(Action<object, Exception> callback, int index)
{
    var callingThread = TaskScheduler.FromCurrentSynchronizationContext();

    Func<object> action = () =>
    {
        return client.Get(index); // Synchronous method, could take a long time
    };

    Action<Task<object>> completeAction = (task) =>
    {
        Exception ex = (task.Exception != null) ? task.Exception.InnerException : task.Exception;

        if (callback != null)
            callback(task.Result, ex);
    };

    Task.Factory.StartNew(action).ContinueWith(completeAction, callingThread);
}

このように、サービスの各非同期メソッドは、最初に呼び出されたスレッド (通常は UI スレッド) をコールバックします。そのため、await/async キーワードがどのように機能するかをシミュレートしています (.NET 4.5 は使用できません)。

このパターンの問題は、"ContinueWith" への最初の呼び出しの後で、UI スレッドに不可解にロックされることです。したがって、この場合、同期関数Getを各プロセスに 5 つのスレッドを生成しようとすると、TaskCreationOptions.LongRunning を指定しようとしても、並列ではなく 1 つずつ実行され、実行中に UI スレッドがブロックされます。

これは、 Task.Factory.StartNew への最初の呼び出しでは決して発生しません。最初の callback 内からの後続の呼び出しでのみ発生します。

4

1 に答える 1

27

新しいスレッドを強制的に起動するには、次のように Task.Factory.StartNew の呼び出しで TaskScheduler.Default を指定する必要があります。

Task.Factory.StartNew(action,
                      CancellationToken.None,
                      TaskCreationOptions.None,
                      TaskScheduler.Default).ContinueWith(completeAction);

私のテストでは、バックグラウンド スレッドを強制するために TaskCreationOptions.LongRunning を指定する必要はありませんが、問題はありません。

于 2013-04-11T19:26:14.783 に答える