我想写一个带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?
当前回答
对于真正想要保持参数的开发人员,这里可能有另一种解决方法。
将参数更改为数组或List以封装实际值。记得在发送到方法之前初始化列表。返回后,在使用它之前一定要检查值是否存在。小心编码。
其他回答
异步方法不接受输出参数的限制仅适用于编译器生成的异步方法,这些方法使用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参数将永远不会完成。
在异步方法中不能有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;
}
您获得了更容易重用代码的能力,而且它比变量或元组更具可读性。
对于真正想要保持参数的开发人员,这里可能有另一种解决方法。
将参数更改为数组或List以封装实际值。记得在发送到方法之前初始化列表。返回后,在使用它之前一定要检查值是否存在。小心编码。
模式匹配来拯救!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}");
}
c# 7+的解决方案是使用隐式元组语法。
private async Task<(bool IsSuccess, IActionResult Result)> TryLogin(OpenIdConnectRequest request)
{
return (true, BadRequest(new OpenIdErrorResponse
{
Error = OpenIdConnectConstants.Errors.AccessDenied,
ErrorDescription = "Access token provided is not valid."
}));
}
返回结果使用方法签名定义的属性名。例句:
var foo = await TryLogin(request);
if (foo.IsSuccess)
return foo.Result;