Лучшая практика для 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
и удалять предупреждение.