Mud*_*Mud 121 c# async-await
刚刚获得VS2012并试图获得处理async.
假设我有一个方法可以从阻塞源中获取一些值.我不希望方法的调用者阻止.我可以编写方法来获取在值到达时调用的回调,但由于我使用的是C#5,我决定使方法异步,因此调用者不必处理回调:
// contrived example (edited in response to Servy's comment)
public static Task<string> PromptForStringAsync(string prompt)
{
return Task.Factory.StartNew(() => {
Console.Write(prompt);
return Console.ReadLine();
});
}
Run Code Online (Sandbox Code Playgroud)
这是一个调用它的示例方法.如果PromptForStringAsync不是异步,则此方法需要在回调中嵌套回调.使用异步,我可以用这种非常自然的方式编写我的方法:
public static async Task GetNameAsync()
{
string firstname = await PromptForStringAsync("Enter your first name: ");
Console.WriteLine("Welcome {0}.", firstname);
string lastname = await PromptForStringAsync("Enter your last name: ");
Console.WriteLine("Name saved as '{0} {1}'.", firstname, lastname);
}
Run Code Online (Sandbox Code Playgroud)
到现在为止还挺好.问题是当我调用 GetNameAsync时:
public static void DoStuff()
{
GetNameAsync();
MainWorkOfApplicationIDontWantBlocked();
}
Run Code Online (Sandbox Code Playgroud)
重点GetNameAsync是它是异步的.我不希望它阻止,因为我想回到MainWorkOfApplicationIDontWantBlocked ASAP并让GetNameAsync在后台执行它的操作.但是,这样调用它会给我一个编译器警告GetNameAsync:
Warning 1 Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
Run Code Online (Sandbox Code Playgroud)
我完全清楚"在完成调用之前,当前方法的执行仍在继续".这是该点的异步代码,对不对?
我更喜欢我的代码在没有警告的情况下进行编译,但是这里没有什么可以"修复"的,因为代码正在完成我打算做的事情.我可以通过存储返回值来摆脱警告GetNameAsync:
public static void DoStuff()
{
var result = GetNameAsync(); // supress warning
MainWorkOfApplicationIDontWantBlocked();
}
Run Code Online (Sandbox Code Playgroud)
但现在我有多余的代码.Visual Studio似乎明白我被迫写了这个不必要的代码,因为它抑制了正常的"从未使用过的值"警告.
我也可以通过将GetNameAsync包装在非异步的方法中来消除警告:
public static Task GetNameWrapper()
{
return GetNameAsync();
}
Run Code Online (Sandbox Code Playgroud)
但那是更多的代码.所以我必须编写我不需要的代码或容忍不必要的警告.
有没有关于我在这里使用异步的问题?
Nik*_*hil 89
如果您确实不需要结果,只需更改GetNameAsync签名即可返回void:
public static async void GetNameAsync()
{
...
}
Run Code Online (Sandbox Code Playgroud)
考虑看一个相关问题的答案: 返回void和返回任务有什么区别?
更新
如果您需要结果,可以更改GetNameAsync为返回,例如Task<string>:
public static async Task<string> GetNameAsync()
{
string firstname = await PromptForStringAsync("Enter your first name: ");
string lastname = await PromptForStringAsync("Enter your last name: ");
return firstname + lastname;
}
Run Code Online (Sandbox Code Playgroud)
并使用如下:
public static void DoStuff()
{
Task<string> task = GetNameAsync();
// Set up a continuation BEFORE MainWorkOfApplicationIDontWantBlocked
Task anotherTask = task.ContinueWith(r => {
Console.WriteLine(r.Result);
});
MainWorkOfApplicationIDontWantBlocked();
// OR wait for the result AFTER
string result = task.Result;
}
Run Code Online (Sandbox Code Playgroud)
Wil*_*lem 48
我讨论的时间已经很晚了,但也可以选择使用#pragma预处理器指令.我在这里和那里有一些异步代码我明确地不想在某些条件下等待,我不喜欢警告和未使用的变量就像其他人一样:
#pragma warning disable 4014
SomeMethodAsync();
#pragma warning restore 4014
Run Code Online (Sandbox Code Playgroud)
该"4014"来源于此MSDN页:编译器警告(等级1)CS4014.
另请参阅@ ryan-horath的警告/答案/sf/answers/850153321/.
在等待的异步调用期间抛出的异常将丢失.要消除此警告,您应该将异步调用的Task返回值分配给变量.这可确保您可以访问抛出的任何异常,这些异常将在返回值中指示.
Joe*_*age 38
我并不特别喜欢将任务分配给未使用的变量或更改方法签名以返回void的解决方案.前者创建了多余的,非直观的代码,而后者可能无法实现,如果您正在实现一个接口或者您想要使用返回的Task的函数的另一种用法.
我的解决方案是创建一个Task的扩展方法,称为DoNotAwait(),它什么都不做.这不仅会抑制所有警告,ReSharper或其他方式,而且会使代码更容易理解,并向未来的维护者表明您真正打算等待调用.
扩展方法:
public static class TaskExtensions
{
public static void DoNotAwait(this Task task) { }
}
Run Code Online (Sandbox Code Playgroud)
用法:
public static void DoStuff()
{
GetNameAsync().DoNotAwait();
MainWorkOfApplicationIDontWantBlocked();
}
Run Code Online (Sandbox Code Playgroud)
编辑添加:这类似于Jonathan Allen的解决方案,如果尚未启动扩展方法将启动任务,但我更喜欢具有单一用途的功能,以便调用者的意图完全清楚.
小智 27
async void很糟糕!
返回void和返回任务有什么区别? http://www.jaylee.org/post/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.aspx http://www.tonicodes.net/blog /为什么任您应该-几乎从不写空隙异步方法/
我建议您通过匿名方法明确运行任务...
即
public static void DoStuff()
{
Task.Run(async () => GetNameAsync());
MainWorkOfApplicationIDontWantBlocked();
}
Run Code Online (Sandbox Code Playgroud)
或者,如果您确实希望它阻止,您可以等待匿名方法
public static void DoStuff()
{
Task.Run(async () => await GetNameAsync());
MainWorkOfApplicationThatWillBeBlocked();
}
Run Code Online (Sandbox Code Playgroud)
但是,如果你的"GetNameAsync"方法必须与UI甚至任何UI绑定进行交互,(WINRT/MVVM,我正在看着你); 然后它有点funkier =)
您需要将引用传递给UI调度程序,如下所示...
Task.Run(async () => await GetNameAsync(CoreApplication.MainView.CoreWindow.Dispatcher));
Run Code Online (Sandbox Code Playgroud)
然后在您的异步方法中,您需要与您的UI或UI绑定元素进行交互,并认为该调度程序...
dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { this.UserName = userName; });
Run Code Online (Sandbox Code Playgroud)
Jon*_*len 16
这就是我目前正在做的事情:
SomeAyncFunction().RunConcurrently();
Run Code Online (Sandbox Code Playgroud)
在哪里RunConcurrently被定义为......
/// <summary>
/// Runs the Task in a concurrent thread without waiting for it to complete. This will start the task if it is not already running.
/// </summary>
/// <param name="task">The task to run.</param>
/// <remarks>This is usually used to avoid warning messages about not waiting for the task to complete.</remarks>
public static void RunConcurrently(this Task task)
{
if (task == null)
throw new ArgumentNullException("task", "task is null.");
if (task.Status == TaskStatus.Created)
task.Start();
}
Run Code Online (Sandbox Code Playgroud)
https://www.nuget.org/packages/Tortuga.Anchor/
根据微软关于此警告的文章,您可以通过简单地将返回的任务分配给变量来解决它.下面是Microsoft示例中提供的代码的翻译:
// To suppress the warning without awaiting, you can assign the
// returned task to a variable. The assignment doesn't change how
// the program runs. However, the recommended practice is always to
// await a call to an async method.
// Replace Call #1 with the following line.
Task delayTask = CalledMethodAsync(delay);
Run Code Online (Sandbox Code Playgroud)
请注意,执行此操作将导致ReSharper中的"Local variable is never used"消息.