Подтвердить что ты не робот

Каков правильный способ связывания задач при возвращении задачи?

Я так использую Tasks в С#, но меня путают, когда я пытаюсь вернуть задачу из метода, и этот метод будет выполнять несколько задач внутри себя. Итак, у меня есть метод, который запускает новую задачу, а затем делает все последовательно внутри? Трудно обернуть вокруг себя все это с помощью .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);
}

Я не уверен, как еще написать этот метод, чтобы он правильно использовал Задачи. Я думаю, я просто чувствую, что должен быть. Континуум с там или еще чем-то.

Возможное исправление

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

Ответ 1

В целом часто бывает лучше избегать новых задач, если вы уже работаете с основанными на задачах методами. Цепочки задач вместо блокировки явно уменьшат накладные расходы системы, так как она не будет поддерживать ожидание потока ThreadPool.

Как говорится, часто проще просто блокировать, как вы делаете.

Обратите внимание, что С# 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;
}

Это позволяет всему процессу работать без создания новой задачи (которая связывает поток threadpool) и никогда не блокирует.

Обратите внимание, что вы, вероятно, захотите добавить продолжения при аннулировании и использовать tcs.SetCancelled, когда была запрошена отмена.

Ответ 2

Вот метод расширения, который я создал для решения этой проблемы. Работает в .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;
}

Ответ 3

Да, все будет выполняться последовательно в рамках основной задачи. Это связано с тем, что вызов свойства Result блокирует текущий поток до тех пор, пока значение не вернется.