我想写一个带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?
当前回答
你不能有带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);
}
其他回答
Alex在可读性上有很好的观点。同样,函数也具有足够的接口来定义返回的类型,并且还可以获得有意义的变量名。
delegate void OpDelegate(int op);
Task<bool> GetDataTaskAsync(OpDelegate callback)
{
bool canGetData = true;
if (canGetData) callback(5);
return Task.FromResult(canGetData);
}
调用者提供一个lambda(或一个命名函数),智能感知通过从委托复制变量名来提供帮助。
int myOp;
bool result = await GetDataTaskAsync(op => myOp = op);
这种特殊的方法类似于“Try”方法,如果方法结果为真,则设置myOp。否则,你不会关心myOp。
您可以通过使用TPL(任务并行库)而不是直接使用await关键字来做到这一点。
private bool CheckInCategory(int? id, out Category category)
{
if (id == null || id == 0)
category = null;
else
category = Task.Run(async () => await _context.Categories.FindAsync(id ?? 0)).Result;
return category != null;
}
if(!CheckInCategory(int? id, out var category)) return error
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;
你不能有带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);
}
out形参的一个很好的特性是,即使函数抛出异常,也可以使用它们返回数据。我认为与异步方法最接近的等效方法是使用一个新对象来保存异步方法和调用者都可以引用的数据。另一种方法是在另一个答案中传递一个委托。
请注意,这两种技术都不具有编译器所具有的任何类型的强制。也就是说,编译器不会要求你在共享对象上设置值或调用传入委托。
下面是一个使用共享对象模拟ref和out的示例实现,用于async方法和其他各种ref和out不可用的场景:
class Ref<T>
{
// Field rather than a property to support passing to functions
// accepting `ref T` or `out T`.
public T Value;
}
async Task OperationExampleAsync(Ref<int> successfulLoopsRef)
{
var things = new[] { 0, 1, 2, };
var i = 0;
while (true)
{
// Fourth iteration will throw an exception, but we will still have
// communicated data back to the caller via successfulLoopsRef.
things[i] += i;
successfulLoopsRef.Value++;
i++;
}
}
async Task UsageExample()
{
var successCounterRef = new Ref<int>();
// Note that it does not make sense to access successCounterRef
// until OperationExampleAsync completes (either fails or succeeds)
// because there’s no synchronization. Here, I think of passing
// the variable as “temporarily giving ownership” of the referenced
// object to OperationExampleAsync. Deciding on conventions is up to
// you and belongs in documentation ^^.
try
{
await OperationExampleAsync(successCounterRef);
}
finally
{
Console.WriteLine($"Had {successCounterRef.Value} successful loops.");
}
}