C# 如何编写带有 out 参数的异步方法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18716928/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to write an async method with out parameter?
提问by jesse
I want to write an async method with an out
parameter, like this:
我想编写一个带有out
参数的异步方法,如下所示:
public async void Method1()
{
int op;
int result = await GetDataTaskAsync(out op);
}
How do I do this in GetDataTaskAsync
?
我如何做到这一点GetDataTaskAsync
?
采纳答案by dcastro
You can't have async methods with ref
or out
parameters.
你不能有带有ref
或out
参数的异步方法。
Lucian Wischik explains why this is not possible on this MSDN thread: http://social.msdn.microsoft.com/Forums/en-US/d2f48a52-e35a-4948-844d-828a1a6deb74/why-async-methods-cannot-have-ref-or-out-parameters
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 方法不支持 out-by-reference 参数?(或 ref 参数?)这是 CLR 的限制。我们选择以类似于迭代器方法的方式实现异步方法——即通过编译器将方法转换为状态机对象。CLR 没有安全的方法将“输出参数”或“引用参数”的地址存储为对象的字段。支持 out-by-reference 参数的唯一方法是通过低级 CLR 重写而不是编译器重写来完成异步功能。我们研究了这种方法,它有很多好处,但它最终会如此昂贵,以至于它永远不会发生。
A typical workaround for this situation is to have the async method return a Tuple instead. You could re-write your method as such:
这种情况的典型解决方法是让异步方法返回一个元组。你可以这样重写你的方法:
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);
}
回答by Alex
You cannot have ref
or out
parameters in async
methods (as was already noted).
方法中不能有ref
orout
参数async
(如前所述)。
This screams for some modelling in the data moving around:
这对移动数据中的某些建模大喊大叫:
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;
}
You gain the ability to reuse your code more easily, plus it's way more readable than variables or tuples.
您可以获得更轻松地重用代码的能力,而且它比变量或元组更具可读性。
回答by Scott Turner
Alex made a great point on readability. Equivalently, a function is also interface enough to define the type(s) being returned and you also get meaningful variable names.
Alex 在可读性方面提出了一个很好的观点。等效地,函数也是足以定义返回类型的接口,并且您还可以获得有意义的变量名称。
delegate void OpDelegate(int op);
Task<bool> GetDataTaskAsync(OpDelegate callback)
{
bool canGetData = true;
if (canGetData) callback(5);
return Task.FromResult(canGetData);
}
Callers provide a lambda (or a named function) and intellisense helps by copying the variable name(s) from the delegate.
调用者提供 lambda(或命名函数),而智能感知通过从委托复制变量名称来提供帮助。
int myOp;
bool result = await GetDataTaskAsync(op => myOp = op);
This particular approach is like a "Try" method where myOp
is set if the method result is true
. Otherwise, you don't care about myOp
.
这种特殊的方法就像一个“尝试”方法,myOp
如果方法结果是,则设置true
。否则,你不在乎myOp
。
回答by binki
One nice feature of out
parameters is that they can be used to return data even when a function throws an exception. I think the closest equivalent to doing this with an async
method would be using a new object to hold the data that both the async
method and caller can refer to. Another way would be to pass a delegate as suggested in another answer.
out
参数的一个很好的特性是,即使函数抛出异常,它们也可用于返回数据。我认为与使用async
方法执行此操作最接近的等价物是使用新对象来保存async
方法和调用者都可以引用的数据。另一种方法是按照另一个答案中的建议传递委托。
Note that neither of these techniques will have any of the sort of enforcement from the compiler that out
has. I.e., the compiler won't require you to set the value on the shared object or call a passed in delegate.
请注意,这些技术都不会从编译器那里获得任何类型的强制执行out
。即,编译器不会要求您在共享对象上设置值或调用传入的委托。
Here's an example implementation using a shared object to imitate ref
and out
for use with async
methods and other various scenarios where ref
and out
aren't available:
下面是使用共享对象模仿示例实现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.");
}
}
回答by jv_
The C#7+ Solutionis to use implicit tuple syntax.
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."
}));
}
return result utilizes the method signature defined property names. e.g:
返回结果利用方法签名定义的属性名称。例如:
var foo = await TryLogin(request);
if (foo.IsSuccess)
return foo.Result;
回答by Paul Marangoni
I think using ValueTuples like this can work. You have to add the ValueTuple NuGet package first though:
我认为像这样使用 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):
}
回答by Jpsy
Here's the code of @dcastro's answer modified for C# 7.0 with named tuples and tuple deconstruction, which streamlines the notation:
这是针对 C# 7.0 修改的@dcastro 答案的代码,带有命名元组和元组解构,简化了符号:
public async void Method1()
{
// Version 1, named tuples:
// just to show how it works
/*
var tuple = await GetDataTaskAsync();
int op = tuple.paramOp;
int result = tuple.paramResult;
*/
// Version 2, tuple deconstruction:
// much shorter, most elegant
(int op, int result) = await GetDataTaskAsync();
}
public async Task<(int paramOp, int paramResult)> GetDataTaskAsync()
{
//...
return (1, 2);
}
For details about the new named tuples, tuple literals and tuple deconstructions see: https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/
有关新命名元组、元组文字和元组解构的详细信息,请参阅:https: //blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/
回答by Michael Gehling
I had the same problem as I like using the Try-method-pattern which basically seems to be incompatible to the async-await-paradigm...
我有同样的问题,因为我喜欢使用 Try-method-pattern 基本上似乎与 async-await-paradigm 不兼容......
Important to me is that I can call the Try-method within a single if-clause and do not have to pre-define the out-variables before, but can do it in-line like in the following example:
对我来说重要的是,我可以在单个 if 子句中调用 Try 方法,而不必预先定义输出变量,但可以像以下示例一样在线执行:
if (TryReceive(out string msg))
{
// use msg
}
So I came up with the following solution:
所以我想出了以下解决方案:
Define a helper struct:
public struct AsyncOutResult<T, OUT> { T returnValue; OUT result; public AsyncOutResult(T returnValue, OUT result) { this.returnValue = returnValue; this.result = result; } public T Result(out OUT result) { result = this.result; return returnValue; } }
Define async Try-method like this:
public async Task<AsyncOutResult<bool, string>> TryReceiveAsync() { string message; bool success; // ... return new AsyncOutResult<bool, string>(success, message); }
Call the async Try-method like this:
if ((await TryReceiveAsync()).Result(out T msg)) { // use msg }
定义一个辅助结构:
public struct AsyncOutResult<T, OUT> { T returnValue; OUT result; public AsyncOutResult(T returnValue, OUT result) { this.returnValue = returnValue; this.result = result; } public T Result(out OUT result) { result = this.result; return returnValue; } }
像这样定义异步尝试方法:
public async Task<AsyncOutResult<bool, string>> TryReceiveAsync() { string message; bool success; // ... return new AsyncOutResult<bool, string>(success, message); }
像这样调用异步 Try 方法:
if ((await TryReceiveAsync()).Result(out T msg)) { // use msg }
If you need multiple out parameters, of course you can define extra structs, like following:
如果需要多个输出参数,当然可以定义额外的结构,如下所示:
public struct AsyncOutResult<T, OUT1, OUT2>
{
T returnValue;
OUT1 result1;
OUT2 result2;
public AsyncOutResult(T returnValue, OUT1 result1, OUT2 result2)
{
this.returnValue = returnValue;
this.result1 = result1;
this.result2 = result2;
}
public T Result(out OUT1 result1, out OUT2 result2)
{
result1 = this.result1;
result2 = this.result2;
return returnValue;
}
}
回答by Jerry Nixon
I love the Try
pattern. It's a tidy pattern.
我喜欢这个Try
图案。这是一个整洁的模式。
if (double.TryParse(name, out var result))
{
// handle success
}
else
{
// handle error
}
But, it's challenging with async
. That doesn't mean we don't have real options. Here are the three core approaches you can consider for async
methods in a quasi-version of the Try
pattern.
但是,它具有挑战性async
。这并不意味着我们没有真正的选择。以下是您可以为模式async
的准版本中的方法考虑的三种核心方法Try
。
Approach 1 - output a structure
方法 1 - 输出结构
This looks most like a sync Try
method only returning a tuple
instead of a bool
with an out
parameter, which we all know is not permitted in C#.
这看起来很像一个同步Try
方法,它只返回一个参数tuple
而不是bool
一个out
参数,我们都知道这在 C# 中是不允许的。
var result = await DoAsync(name);
if (result.Success)
{
// handle success
}
else
{
// handle error
}
With a method that returns true
of false
and never throws an exception
.
与回报的方法true
的false
,从来没有抛出exception
。
Remember, throwing an exception in a
Try
method breaks the whole purpose of the pattern.
请记住,在
Try
方法中抛出异常会破坏模式的全部目的。
async Task<(bool Success, StorageFile File, Exception exception)> DoAsync(string fileName)
{
try
{
var folder = ApplicationData.Current.LocalCacheFolder;
return (true, await folder.GetFileAsync(fileName), null);
}
catch (Exception exception)
{
return (false, null, exception);
}
}
Approach 2 - pass in callback methods
方法 2 - 传入回调方法
We can use anonymous
methods to set external variables. It's clever syntax, though slightly complicated. In small doses, it's fine.
我们可以使用anonymous
方法来设置外部变量。这是聪明的语法,虽然有点复杂。小剂量,没问题。
var file = default(StorageFile);
var exception = default(Exception);
if (await DoAsync(name, x => file = x, x => exception = x))
{
// handle success
}
else
{
// handle failure
}
The method obeys the basics of the Try
pattern but sets out
parameters to passed in callback methods. It's done like this.
该方法遵循Try
模式的基础,但设置out
参数以在回调方法中传递。它是这样完成的。
async Task<bool> DoAsync(string fileName, Action<StorageFile> file, Action<Exception> error)
{
try
{
var folder = ApplicationData.Current.LocalCacheFolder;
file?.Invoke(await folder.GetFileAsync(fileName));
return true;
}
catch (Exception exception)
{
error?.Invoke(exception);
return false;
}
}
There's a question in my mind about performance here. But, the C# compiler is so freaking smart, that I think you're safe choosing this option, almost for sure.
我心中有一个关于性能的问题。但是,C# 编译器非常聪明,我认为您几乎可以肯定选择此选项是安全的。
Approach 3 - use ContinueWith
方法 3 - 使用 ContinueWith
What if you just use the TPL
as designed? No tuples. The idea here is that we use exceptions to redirect ContinueWith
to two different paths.
如果你只是TPL
按照设计使用怎么办?没有元组。这里的想法是我们使用异常重定向ContinueWith
到两个不同的路径。
await DoAsync(name).ContinueWith(task =>
{
if (task.Exception != null)
{
// handle fail
}
if (task.Result is StorageFile sf)
{
// handle success
}
});
With a method that throws an exception
when there is any kind of failure. That's different than returning a boolean
. It's a way to communicate with the TPL
.
使用一种exception
在出现任何类型的失败时抛出 an 的方法。这与返回 a 不同boolean
。这是一种与TPL
.
async Task<StorageFile> DoAsync(string fileName)
{
var folder = ApplicationData.Current.LocalCacheFolder;
return await folder.GetFileAsync(fileName);
}
In the code above, if the file is not found, an exception is thrown. This will invoke the failure ContinueWith
that will handle Task.Exception
in its logic block. Neat, huh?
在上面的代码中,如果找不到文件,则抛出异常。这将调用ContinueWith
将Task.Exception
在其逻辑块中处理的故障。整洁吧?
Listen, there's a reason we love the
Try
pattern. It's fundamentally so neat and readable and, as a result, maintainable. As you choose your approach, watchdog for readability. Remember the next developer who in 6 months and doesn't have you to answer clarifying questions. Your code can be the only documentation a developer will ever have.
听着,我们喜欢这种
Try
模式是有原因的。从根本上说,它是如此整洁和可读,因此,它是可维护的。当你选择你的方法时,看门狗的可读性。记住下一个开发人员,他在 6 个月内没有让您回答澄清问题。您的代码可能是开发人员将拥有的唯一文档。
Best of luck.
祝你好运。
回答by Theodor Zoulias
The limitation of the async
methods not accepting out
parameters applies only to the compiler-generated async methods, these declared with the async
keyword. It doesn't apply to hand-crafted async methods. In other words it is possible to create Task
returning methods accepting out
parameters. For example lets say that we already have a ParseIntAsync
method that throws, and we want to create a TryParseIntAsync
that doesn't throw. We could implement it like this:
async
不接受out
参数的方法的限制仅适用于编译器生成的异步方法,这些方法用async
关键字声明。它不适用于手工制作的异步方法。换句话说,可以创建Task
接受out
参数的返回方法。例如,假设我们已经有一个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);
}
Using the TaskCompletionSource
and the ContinueWith
method is a bit awkward, but there is no other option since we can't use the convenient await
keyword inside this method.
使用TaskCompletionSource
andContinueWith
方法有点尴尬,但没有其他选择,因为我们不能await
在这个方法中使用方便的关键字。
Usage example:
用法示例:
if (await TryParseIntAsync("-13", out var result))
{
Console.WriteLine($"Result: {await result}");
}
else
{
Console.WriteLine($"Parse failed");
}
Update:If the async logic is too complex to be expressed without await
, then it could be encapsulated inside a nested asynchronous anonymous delegate. A TaskCompletionSource
would still be needed for the out
parameter. It is possible that the out
parameter could be completed before
the completion of the main task, as in the example bellow:
更新:如果异步逻辑太复杂而无法在没有 的情况下表达await
,则可以将其封装在嵌套的异步匿名委托中。参数TaskCompletionSource
仍然需要A。out
这可能是该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);
}))();
}
This example assumes the existence of three asynchronous methods GetResponseAsync
, GetRawDataAsync
and FilterDataAsync
that are called
in succession. The out
parameter is completed on the completion of the second method. The GetDataAsync
method could be used like this:
此示例假定存在三个异步方法GetResponseAsync
,GetRawDataAsync
并且FilterDataAsync
这些方法被连续调用。的out
参数完成在第二方法的完成。该GetDataAsync
方法可以这样使用:
var data = await GetDataAsync("http://example.com", out var rawDataLength);
Console.WriteLine($"Data: {data}");
Console.WriteLine($"RawDataLength: {await rawDataLength}");
Awaiting the data
before awaiting the rawDataLength
is important in this simplified example, because in case of an exception the out
parameter will never be completed.
在这个简化的示例中,等待data
before 等待rawDataLength
很重要,因为在出现异常的情况下,out
参数将永远不会完成。