Как получить параметр out асинхронного метода, который выполняется в другом потоке - PullRequest
0 голосов
/ 17 октября 2019

Я создал класс Out<T>, намереваясь использовать его в качестве параметра out в асинхронных методах.

class Out<T>
{
    public T Value { get; set; }
}

Вот пример того, как я собираюсь использовать его:

async Task DoWorkAsync(Out<int> arg)
{
    arg.Value = 13;
    await Task.Delay(500); // Placeholder for a really useful async operation
}

Value всегда будет назначаться перед первым await внутри асинхронного метода. Другими словами, назначение будет происходить в синхронной части метода. Таким образом, вызывающая сторона может получить значение сразу после создания Task и использовать его до ожидания Task:

var arg = new Out<int>();
var task = DoWorkAsync(arg);
Console.WriteLine(arg.Value); // Prints '13' in the console :-)
await task;

До этого момента все работает отлично. Проблема возникает, когда становится обязательным запуск метода DoWorkAsync в потоке пула потоков. Сначала я попробовал самый простой подход с Task.Run:

var arg = new Out<int>();
var task = Task.Run(() => DoAsync(arg));
Console.WriteLine(arg.Value); // Prints '0' in the console :-(
await task;

Это не сработало. Еще одна попытка:

var arg = new Out<int>();
var task = Task.Run(async () => await DoWorkAsync(arg));
Console.WriteLine(arg.Value); // Prints '0' in the console :-(
await task;

Не сработало. После долгих раздумий я понял, что текущий поток не может получить arg.Value, пока поток пула потоков не обработает синхронную часть метода, поэтому текущий поток должен ждать. Затем, после получения arg.Value, текущий поток должен еще раз подождать асинхронную часть метода. Это привело меня к решению проблемы:

var arg = new Out<int>();
var task = await Task.Factory.StartNew(() => DoWorkAsync(arg),
    CancellationToken.None, TaskCreationOptions.DenyChildAttach,
    TaskScheduler.Default);
Console.WriteLine(arg.Value); // Prints '13' in the console :-/
await task;

Это сработало, но мне не нравится использование Task.Factory.StartNew. Это метод старой школы, с плохой репутацией , опасный . Поэтому мой вопрос: возможно ли решить эту проблему, не используя метод Task.Factory.StartNew?

1 Ответ

0 голосов
/ 17 октября 2019

Вашему Task.Run нужно Wait(), чтобы дождаться завершения задачи.

Быстрый тест с Stopwatch даст нам представление:

Stopwatch sw = new Stopwatch();

var arg = new Out<int>();
sw.Start();
var task = Task.Run(() => DoWorkAsync(arg));
sw.Stop();

Console.WriteLine("Elapsed: " + sw.ElapsedMilliseconds + "ms");

Console.WriteLine(arg.Value); // Prints '0' in the console :-(

Результаты:

Elapsed: 11ms
0

, как вы можете видеть, Task.Run didnне дождусь Task.Delay(500).

в то время как при использовании Wait()

Stopwatch sw = new Stopwatch();

var arg = new Out<int>();
sw.Start();
Task.Run(() => DoWorkAsync(arg)).Wait();
sw.Stop();

Console.WriteLine("Elapsed: " + sw.ElapsedMilliseconds);

Console.WriteLine(arg.Value); // Prints '0' in the console :-(

результаты:

Elapsed: 518ms
13

ожидали завершения задачи.

сравнить с асинхронным ожиданием:

Stopwatch sw = new Stopwatch();

var arg = new Out<int>();
sw.Start();
var task = DoWorkAsync(arg);
await task;
sw.Stop();

Console.WriteLine("Elapsed: " + sw.ElapsedMilliseconds);

Console.WriteLine(arg.Value); // Prints '13' in the console :-)

результаты:

Elapsed: 516
13
...