Лучшая практика для ITargetBlock.Завершение.ContinueWith()
Этот вопрос касается лучших практик при использовании ContinueWith() для обработки завершения блока данных TPL.
ITargetBlock<TInput>.Completion() метод позволяет асинхронно обрабатывать завершение блока данных с помощью ContinueWith().
private static void Main()
{
test().Wait();
}
static async Task test()
{
var transform = new TransformBlock<int, double>(i => i/2.0);
var output = new ActionBlock<double>(d => Console.WriteLine(d));
// Warning CS4014 here:
transform.Completion.ContinueWith(continuation => output.Complete());
transform.LinkTo(output);
for (int i = 0; i < 10; ++i)
await transform.SendAsync(i);
transform.Complete();
await output.Completion;
}
Код имеет очень простой вид:TransformBlock который делит целые числа на 2,0 и превращает их в двойники. Преобразованные данные обрабатываются с помощью ActionBlock который просто выводит значения в окно консоли.
Вывод:
0
0.5
1
1.5
2
2.5
3
3.5
4
4.5
Когда TransformBlock будет завершено, я хочу также завершить ActionBlock. Это удобно делать так:
transform.Completion.ContinueWith(continuation => output.Complete());
И здесь кроется проблема. Поскольку это находится внутри метода async и я игнорирую возвращаемое значение из ContinueWith(), я получаю предупреждение компилятора:
Предупреждение CS4014: поскольку этот вызов не ожидается, выполнение текущего метода продолжается до завершения вызова. Рассмотрите возможность применения оператора 'await' к результату вызова.
Ясно, что я не могу await выполнить вызов, как советует предупреждение - если я это сделаю, код зависнет, потому что задача CompleteWith() не может быть завершена, пока не будет вызвана transform.Complete().
Теперь у меня нет проблем с самим предупреждением (я могу просто подавить его или назначить задачу переменной, или использовать расширение задачи .Forget() и так далее) - но вот мой вопросы:
- безопасно ли таким образом обрабатывать связанное завершение блока данных?
- есть ли лучший способ? Есть ли здесь что-нибудь важное, что я упустил из виду?
Я думаю, что если бы я делал что - то другое, чем output.Complete() внутри продолжения, я должен был бы обеспечить обработку исключений-но, возможно, даже просто вызов output.Complete() чреват опасностью?
1 ответ:
В то время как вы можете реализовать распространение завершения самостоятельно, поток данных TPL делает это за вас с помощью
DataflowLinkOptions.PropagateCompletion:transform.LinkTo(output, new DataflowLinkOptions {PropagateCompletion = true});
Если вы все еще хотите реализовать это самостоятельно, вы можете решить проблему, сохранив задачу и ожидая ее, когда это уместно, используя
Task.WhenAll:static async Task test() { var transform = new TransformBlock<int, double>(i => i/2.0); var output = new ActionBlock<double>(d => Console.WriteLine(d)); // Warning CS4014 here: var continuation = transform.Completion.ContinueWith(_ => output.Complete()); transform.LinkTo(output); for (int i = 0; i < 10; ++i) await transform.SendAsync(i); transform.Complete(); await Task.WhenAll(continuation, output.Completion) }Это позволяет обрабатывать любые исключения в
output.Completeи удалять предупреждение.