简体   繁体   中英

How to retrieve the out parameter of an asynchronous method that runs on another thread

I created a class Out<T> , intending to use it as an out parameter in async methods.

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

Here is an example of how I intend to use it:

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

The Value will always be assigned before the first await inside the asynchronous method. In other words the assignment will happen in the synchronous part of the method. So the caller can retrieve the value immediately after creating the Task , and use it before the awaiting of the Task :

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

Until this point everything works perfectly. The problem appears when it becomes mandatory for the DoWorkAsync method to run in a ThreadPool thread. Initially I tried the simplest approach with Task.Run :

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

This didn't work. Another try:

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

Didn't work either. After much thought I realized that the current thread cannot retrieve the arg.Value until the ThreadPool thread has processed the synchronous part of the method, so the current thread must wait. Then, after retrieving the arg.Value , the current thread must wait one more time for the asynchronous part of the method. This led me to a solution of the problem:

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;

This worked, but I dislike the use of Task.Factory.StartNew . This is an old-school method, with a bad reputation of being dangerous . So my question is: Is it possible to solve this problem, without using the Task.Factory.StartNew method?


Clarification: The reason it is mandatory for the DoWorkAsync method to run in a ThreadPool thread is because it contains a call to a third-party API that I don't trust, and so by calling it directly in the UI thread I risk freezing the UI. So my goal is to retrieve the Out.Value immediately after the DoWorkAsync returns, without invoking it in the current thread.

Your Task.Run needs Wait() in order to wait for the task to be completed.

A quick test with Stopwatch will give us an idea:

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 :-(

The results:

Elapsed: 11ms
0

as you can see, the Task.Run didn't wait for the Task.Delay(500) .

while if you use 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 :-(

results:

Elapsed: 518ms
13

it waited for the task to be completed.

compare it with asynchronously wait:

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 :-)

results:

Elapsed: 516
13

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM