26

私は C# で Tasks を使用していますが、メソッドから Task を返そうとすると混乱し、そのメソッドはそれ自体で複数のタスクを実行します。では、メソッドに新しいタスクをスピンアップさせてから、その中ですべてを順番に実行する必要がありますか? .ContinueWith() ですべてを行うのは難しいです

例:

public Task<string> GetSomeData(CancellationToken token)
{
    return Task.Factory.StartNew(() =>
    {
        token.ThrowIfCancellationRequested();

        var initialData = GetSomeInteger(token).Result;

        return GetSomeString(initialData, token).Result;
    });
}

public Task<int> GetSomeInteger(CancellationToken token)
{
    return Task<int>.Factory.StartNew(() =>
    {
        return 4;
    }, token);
}

public Task<string> GetSomeString(int value, CancellationToken token)
{
    return Task<string>.Factory.StartNew(() =>
    {
        return value.ToString();
    }, token);
}

タスクを正しく使用するために、このメソッドを他にどのように記述すればよいかわかりません。そこに .ContinueWith か何かがあるべきだと思うだけだと思います。

可能な修正??

public Task<string> GetSomeData(CancellationToken token)
{
    return GetSomeInteger(token).ContinueWith((prevTask) =>
    {
        return GetSomeString(prevTask.Result, token);
    }, token).Unwrap();
}
4

3 に答える 3

32

一般に、既にタスクベースの方法を使用している場合は、新しいタスクをスピンアップしないようにすることが最善の方法です。明示的にブロックする代わりにタスクをチェーンすると、システムのオーバーヘッドが削減されます。これは、ThreadPool スレッドを待機させたままにしておくことがないためです。

そうは言っても、あなたがやっているように単にブロックする方が簡単なことがよくあります.

C# 5 ではこれがはるかに簡単になり、両方の長所を活かす API が提供されることに注意してください。

public async Task<string> GetSomeData(CancellationToken token)
{
    token.ThrowIfCancellationRequested();

    var initialData = await SomeOtherMethodWhichReturnsTask(token);

    string result = await initialData.MethodWhichAlsoReturnsTask(token);

    return result;
};

更新後の編集:

新しいコードを考えると、これを直接チェーンする簡単な方法はありませんContinueWith。いくつかのオプションがあります。Unwrapを使用して、作成したものを変換できTask<Task<string>>ます。つまり、次のようになります。

public Task<string> GetSomeData(CancellationToken token)
{
    Task<Task<string>> task = GetSomeInteger(token)
                               .ContinueWith(t => 
                               {
                                   return GetSomeString(t.Result, token);
                               }, token);
    return task.Unwrap();
}

別の方法として、次の方法でアンラップをエレガントに処理できますTaskCompletionSource<T>

public Task<string> GetSomeData(CancellationToken token)
{
    var tcs = new TaskCompletionSource<string>();

    Task<int> task1 = GetSomeInteger(token);
    Task<Task<string>> task2 = task1.ContinueWith(t => GetSomeString(t.Result, token));
    task2.ContinueWith(t => tcs.SetResult(t.Result.Result));
    return tcs.Task;
}

これにより、新しいタスク (スレッドプール スレッドを結び付ける) を作成せずに、またブロックすることなく、プロセス全体を機能させることができます。

キャンセル時に継続を追加し、キャンセルが要求されたときにもtcs.SetCancelledを使用することに注意してください。

于 2012-08-02T18:22:12.220 に答える
5

これを解決するために作成した拡張メソッドを次に示します。.Net 4+ で動作

public static Task<TNewResult> ContinueWith<T, TNewResult>(this Task<T> task, Func<Task<T>, Task<TNewResult>> continuationFunction, CancellationToken cancellationToken)
{
    var tcs = new TaskCompletionSource<TNewResult>();
    task.ContinueWith(t => 
    {
        if (cancellationToken.IsCancellationRequested)
        {
            tcs.SetCanceled();
        }
        continuationFunction(t).ContinueWith(t2 => 
        {
            if (cancellationToken.IsCancellationRequested || t2.IsCanceled)
            {
                tcs.TrySetCanceled();
            }
            else if (t2.IsFaulted)
            {
                tcs.TrySetException(t2.Exception);
            }
            else
            {
                tcs.TrySetResult(t2.Result);
            }
        });
    });
    return tcs.Task;
}
于 2014-08-24T03:09:51.917 に答える
0

はい、すべてがメインタスク内で順番に実行されます。これは、Resultプロパティを呼び出すと、値が返されるまで現在のスレッドがブロックされるためです。

于 2012-08-02T18:16:00.893 に答える