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.