我想写一个带out参数的async方法,像这样:
public async void Method1()
{
int op;
int result = await GetDataTaskAsync(out op);
}
我如何做到这一点在GetDataTaskAsync?
我想写一个带out参数的async方法,像这样:
public async void Method1()
{
int op;
int result = await GetDataTaskAsync(out op);
}
我如何做到这一点在GetDataTaskAsync?
当前回答
我认为像这样使用ValueTuples是可行的。你必须先添加ValueTuple NuGet包:
public async void Method1()
{
(int op, int result) tuple = await GetDataTaskAsync();
int op = tuple.op;
int result = tuple.result;
}
public async Task<(int op, int result)> GetDataTaskAsync()
{
int x = 5;
int y = 10;
return (op: x, result: y):
}
其他回答
模式匹配来拯救!c# 9(我认为)之后:
// example of a method that would traditionally would use an out parameter
public async Task<(bool success, int? value)> TryGetAsync()
{
int? value = // get it from somewhere
return (value.HasValue, value);
}
像这样使用它:
if (await TryGetAsync() is (true, int value))
{
Console.WriteLine($"This is the value: {value}");
}
你不能有带ref或out参数的异步方法。
Lucian Wischik解释了为什么这是不可能的MSDN线程:http://social.msdn.microsoft.com/Forums/en-US/d2f48a52-e35a-4948-844d-828a1a6deb74/why-async-methods-cannot-have-ref-or-out-parameters
As for why async methods don't support out-by-reference parameters? (or ref parameters?) That's a limitation of the CLR. We chose to implement async methods in a similar way to iterator methods -- i.e. through the compiler transforming the method into a state-machine-object. The CLR has no safe way to store the address of an "out parameter" or "reference parameter" as a field of an object. The only way to have supported out-by-reference parameters would be if the async feature were done by a low-level CLR rewrite instead of a compiler-rewrite. We examined that approach, and it had a lot going for it, but it would ultimately have been so costly that it'd never have happened.
对于这种情况,一个典型的解决方法是让async方法返回一个元组。 你可以像这样重写你的方法:
public async Task Method1()
{
var tuple = await GetDataTaskAsync();
int op = tuple.Item1;
int result = tuple.Item2;
}
public async Task<Tuple<int, int>> GetDataTaskAsync()
{
//...
return new Tuple<int, int>(1, 2);
}
异步方法不接受输出参数的限制仅适用于编译器生成的异步方法,这些方法使用async关键字声明。它不适用于手工制作的异步方法。换句话说,可以创建Task返回接受输出参数的方法。例如,假设我们已经有一个会抛出的ParseIntAsync方法,我们想创建一个不抛出的TryParseIntAsync方法。我们可以这样实现它:
public static Task<bool> TryParseIntAsync(string s, out Task<int> result)
{
var tcs = new TaskCompletionSource<int>();
result = tcs.Task;
return ParseIntAsync(s).ContinueWith(t =>
{
if (t.IsFaulted)
{
tcs.SetException(t.Exception.InnerException);
return false;
}
tcs.SetResult(t.Result);
return true;
}, default, TaskContinuationOptions.None, TaskScheduler.Default);
}
使用TaskCompletionSource和ContinueWith方法有点尴尬,但是没有其他选择,因为我们不能在这个方法中使用方便的await关键字。
使用的例子:
if (await TryParseIntAsync("-13", out var result))
{
Console.WriteLine($"Result: {await result}");
}
else
{
Console.WriteLine($"Parse failed");
}
更新:如果异步逻辑太复杂,没有await就无法表达,那么可以将它封装在嵌套的异步匿名委托中。out参数仍然需要TaskCompletionSource。out参数可以在之前完成 主任务的完成,如下例所示:
public static Task<string> GetDataAsync(string url, out Task<int> rawDataLength)
{
var tcs = new TaskCompletionSource<int>();
rawDataLength = tcs.Task;
return ((Func<Task<string>>)(async () =>
{
var response = await GetResponseAsync(url);
var rawData = await GetRawDataAsync(response);
tcs.SetResult(rawData.Length);
return await FilterDataAsync(rawData);
}))();
}
这个例子假设存在三个异步方法GetResponseAsync, GetRawDataAsync和FilterDataAsync被调用 在继承。out参数在第二个方法完成时完成。GetDataAsync方法可以这样使用:
var data = await GetDataAsync("http://example.com", out var rawDataLength);
Console.WriteLine($"Data: {data}");
Console.WriteLine($"RawDataLength: {await rawDataLength}");
在这个简化的示例中,在等待rawDataLength之前等待数据是很重要的,因为在异常的情况下,out参数将永远不会完成。
我有同样的问题,因为我喜欢使用Try-method-pattern,基本上似乎与async- wait-paradigm不兼容…
对我来说重要的是,我可以在一个if-子句中调用try -方法,而不必预先定义out-变量,但可以像下面的例子那样内联执行:
if (TryReceive(out string msg))
{
// use msg
}
所以我提出了以下解决方案:
注意:新的解决方案更优越,因为它可以与在这里的许多其他答案中描述的仅返回元组的方法一起使用,这可能经常在现有代码中找到!
新的解决方案:
Create extension methods for ValueTuples: public static class TupleExtensions { public static bool TryOut<P2>(this ValueTuple<bool, P2> tuple, out P2 p2) { bool p1; (p1, p2) = tuple; return p1; } public static bool TryOut<P2, P3>(this ValueTuple<bool, P2, P3> tuple, out P2 p2, out P3 p3) { bool p1; (p1, p2, p3) = tuple; return p1; } // continue to support larger tuples... } Define async Try-method like this: public async Task<(bool, string)> TryReceiveAsync() { string message; bool success; // ... return (success, message); } Call the async Try-method like this: if ((await TryReceiveAsync()).TryOut(out string msg)) { // use msg }
旧的解决方案:
Define a helper struct: public struct AsyncOut<T, OUT> { private readonly T returnValue; private readonly OUT result; public AsyncOut(T returnValue, OUT result) { this.returnValue = returnValue; this.result = result; } public T Out(out OUT result) { result = this.result; return returnValue; } public T ReturnValue => returnValue; public static implicit operator AsyncOut<T, OUT>((T returnValue ,OUT result) tuple) => new AsyncOut<T, OUT>(tuple.returnValue, tuple.result); } Define async Try-method like this: public async Task<AsyncOut<bool, string>> TryReceiveAsync() { string message; bool success; // ... return (success, message); } Call the async Try-method like this: if ((await TryReceiveAsync()).Out(out string msg)) { // use msg }
对于多个out参数,您可以定义额外的结构(例如AsyncOut<T,OUT1, OUT2>),或者您可以返回一个元组。
在异步方法中不能有ref或out参数(如前所述)。
这需要在数据移动中进行一些建模:
public class Data
{
public int Op {get; set;}
public int Result {get; set;}
}
public async void Method1()
{
Data data = await GetDataTaskAsync();
// use data.Op and data.Result from here on
}
public async Task<Data> GetDataTaskAsync()
{
var returnValue = new Data();
// Fill up returnValue
return returnValue;
}
您获得了更容易重用代码的能力,而且它比变量或元组更具可读性。