[Перевод] Базовый вариант использования Task Unwrap

fa82da7505e81350e9b2611cc92b4f91

Введение

Недавно, после нескольких месяцев отсутствия использования .Net/C#, я улучшал существующее приложение .Net/C# WPF, используя .Net Task Parallel Library (TPL).

Но, наивно применяя шаблоны обещаний JavaScript, которые я использовал в предыдущие месяцы, я столкнулся со странной проблемой, которая заставила меня использовать довольно экзотический метод расширения Unwrap.

В этой статье описывается проблема, объясняется ее причина, предлагается исправление с помощью Unwrap и, наконец, представлена ​​более современная версия с парадигмой async/await C# 5.0.

Простой рабочий процесс в JavaScript с Promises

Вот JavaScript-реализация простого рабочего процесса, состоящего из трех шагов, второй из которых имитирует отложенную обработку с помощью setTimeout с использованием Promise API:

function doFirstThing() {
	return new Promise(resolve => {
		console.log("First thing done")
		resolve()
	})
}

function doSecondThing() {
	return new Promise(resolve => {
		setTimeout(() => {
			console.log("Second thing done")
			resolve()
		}, 1000)
	})
}

function doThirdThing() {
	return new Promise(resolve => {
		console.log("Third thing done")
		resolve()
	})
}

doFirstThing().then(doSecondThing).then(doThirdThing)

Вот результат после запуска с Node:

$ node test.js
First thing done
Second thing done
Third thing done

Реализация C# с задачами

Вот тот же рабочий процесс, реализованный на C# с использованием .Net TPL:

using System;
using System.Threading.Tasks;

namespace Test
{
    class Program
    {
        static Task DoFirstThing()
        {
            return Task.Run(() => Console.WriteLine("First thing done"));
        }

        static Task DoSecondThing()
        {
            return Task.Delay(1000).ContinueWith(_ => Console.WriteLine("Second thing done"));
        }

        static Task DoThirdThing()
        {
            return Task.Run(() => Console.WriteLine("Third thing done"));
        }

        static void Main(string[] args)
        {
            DoFirstThing().ContinueWith(_ => DoSecondThing()).ContinueWith(_ => DoThirdThing());

            Console.ReadLine();
        }
    }
}

Обратите внимание, что в отличие от обещаний JavaScript, задачи .Net не запускаются/планируются автоматически при создании, поэтому необходимо явно вызывать Run.

Here is the result:

First thing done
Third thing done
Second thing done

Как видите, третий шаг выполняется раньше второго!

Это связано с тем, что ContinueWith создает новую задачу, обертывающую предоставленную обработку, которая состоит только в вызове DoSecondThing (который сам создает вторую задачу), который немедленно возвращает результат.

ContinueWith не будет учитывать результирующую задачу, в отличие от Promise.then, который обрабатывает случай возврата обещания определенным образом: обещание, возвращенное к тому времени, будет разрешено только тогда, когда будет выполнено базовое обещание.

Unwrap в помощь

Чтобы получить поведение обещаний JavaScript, нам нужно явно сообщить TPL, что мы хотим рассмотреть базовую задачу, используя Unwrap (реализованный как метод расширения, предоставляемый классом TaskExtensions):

DoFirstThing()
  .ContinueWith(_ => DoSecondThing())
  .Unwrap()
  .ContinueWith(_ => DoThirdThing());

Результат теперь соответствует JavaScript:

First thing done
Second thing done
Third thing done

Более современный способ реализации await

В C# 5.0 добавлен некоторый синтаксический сахар, чтобы упростить использование TPL с оператором ожидания (await operator):

await DoFirstThing();
await DoSecondThing();
await DoThirdThing();

await внутренне вызывает Unwrap и ожидает выполнения базовой задачи, как и ожидалось, и дает тот же результат.

Обратите внимание, что await можно использовать только в асинхронном методе.

Заключение

Сопоставление между языками и платформами не всегда очевидно, но, к счастью, в настоящее время все они копируют друг друга и в конечном итоге предлагают одни и те же парадигмы и API, такие как дуэт async/await, который вы используете почти одинаково как в C#, так и в JavaScript.

© Habrahabr.ru