如果在using语句中进行异步调用,并且异步处理调用的结果(即发生这种情况的方法是异步并在加载和处理结果之前返回),using语句是否可以输出范围? 换句话说,做这样的事情是否安全:
async void LoadAndProcessStuff()
{
using(var ctx = CreateDBContext()){
someResource.LoadStuffsAsync().ForEachAsync(stuff => ctx.Stuffs.Add(stuff));
}
}
或
async void LoadAndProcessStuff2()
{
using(var ctx = CreateDBContext()){
ctx.Stuffs.Select(stuff => someResource.LoadMoreStuffsAsync(stuff))
.ForEachAsync(stuff => ctx.Stuffs2.AddRange(stuff.Result));
}
}
或者可以在调用ForEachAsync时处理ctx并导致异常?
答案 0 :(得分:1)
在异步方法中,“ using”关键字应该是安全的。编译器只是将其重写为“最终”表达式,该表达式的工作方式类似于异步方法中的所有其他异常处理。请注意,Dispose()不应阻塞或长时间运行,而应仅释放资源-除非您想削弱服务器。
对于安全情况,这是一个简单的测试用例:
using System;
using System.Threading.Tasks;
namespace so {
sealed class Garbage : IDisposable {
public void Dispose() {
Console.WriteLine("Disposing Garbage");
}
}
// Garbage is safely Disposed() only after delay is done
class Program {
public static async Task Main() {
using (Garbage g = new Garbage()) {
Console.WriteLine("Delay Starting");
await Task.Delay(1000);
Console.WriteLine("Delay Finished");
}
}
}
}
但是,如果您有一个非异步方法返回一个Task而不是等待它,则它可能是不安全的。这是因为同步方法仅被称为一次。编译器没有生成协程或状态机,因此有可能在Task仍在运行时立即调用Dispose()。
public Task DoWorkAsync()
{
using (var service = new Service())
{
var arg1 = ComputeArg();
var arg2 = ComputeArg();
// NOT SAFE - service will be disposed
// There is a high-probability that this idiom results
// in an ObjectDisposedException
return service.AwaitableMethodAsync(arg1, arg2);
}
}
对于裁判:http://www.thebillwagner.com/Blog/Item/2017-05-03-ThecuriouscaseofasyncawaitandIDisposable
答案 1 :(得分:-3)
如果您想安全地执行异步/并行内容(以及后台内容),最好使用Tasks
,async/await
和ConfigureAwait
。请记住,在执行离开方法之前运行using
内的东西总是更好,因此您必须相应地思考和封装代码。
以下是您可能想要做的一些示例:
public async Task ParentMethodAsync() {
DoSomeSyncStuff();
await DoBigStuffAsync();
DoSomeSyncStuffAfterAsyncBigStuff();
}
public async Task DoBigStuffAsync() {
await Task.Run(() => {
DoBigSyncStuff();
});
}
使用该代码,您的执行将是:
DoSomeSyncStuff
DoBigSyncStuff
将在内部异步运行
DoBigStuffAsync
ParentMethodAsync
将在运行前等待此操作完成
DoSomeSyncStuffAfterAsyncBigStuff
public async Task ParentMethodAsync() {
DoSomeSyncStuff();
// original context/thread
await DoBigStuffAsync();
// same context/thread as original
DoSomeSyncStuffAfterAsyncBigStuff();
}
public async Task DoBigStuffAsync() {
// here you are on the original context/thread
await Task.Run(() => {
// this will run on a background thread if possible
DoBigSyncStuff();
}).ConfigureAwait(false);
// here the context/thread will not be the same as original one
}
这里有相同的运行顺序和阻止点,但是ConfigureAwait(false)
指定您不关心原始上下文的同步。请注意ParentMethodAsync
上下文/线程不受影响
public async Task ParentMethodAsync() {
DoSomeSyncStuff();
Task bigStuffTask = DoBigStuffAsync();
DoSomeSyncStuffBeforeEndOfBigStuff();
await bigStuffTask;
DoAnotherSyncStuffAfterAsyncBigStuff();
}
public async Task DoBigStuffAsync() {
await Task.Run(() => {
DoBigSyncStuff();
});
}
使用该代码,您的执行将是:
DoSomeSyncStuff
DoBigSyncStuff
将开始在内部异步运行
DoBigStuffAsync
ParentMethodAsync
不会等待bigStuffTask
完成并运行DoSomeSyncStuffBeforeEndOfBigStuff
bigStuffTask
(或DoBigStuffAsync
)可以在之前或之后完成
DoSomeSyncStuffBeforeEndOfBigStuff
确实await bigStuffTask
将迫使ParentMethodAsync
等待
bigStuffTask
在运行之前完成
DoAnotherSyncStuffAfterAsyncBigStuff
public async Task ParentMethodAsync() {
DoSomeSyncStuff();
Task bigStuffTask = DoBigStuffAsync();
Task bigStuff2Task = DoBigStuff2Async();
await Task.WhenAll(bigStuffTask, bigStuff2Task);
DoAnotherSyncStuffAfterAsyncBigStuff();
}
public async Task DoBigStuffAsync() {
await Task.Run(() => {
DoBigSyncStuff();
});
}
使用该代码,您的执行将是:
DoSomeSyncStuff
DoBigSyncStuff
将开始在DoBigStuffAsync
bigStuff2Task
将开始在DoBigStuff2Async
ParentMethodAsync
将等待bigStuffTask
和bigStuff2Task
完成DoAnotherSyncStuffAfterAsyncBigStuff
将(同步)public async Task ParentMethodAsync() {
DoSomeSyncStuff();
Task bigStuffTask = DoBigStuffAsync();
Task bigStuff2Task = DoBigStuff2Async();
// this one is fired and forgotten
DoFireAndForgetStuffAsync();
await Task.WhenAll(bigStuffTask, bigStuff2Task);
DoAnotherSyncStuffAfterAsyncBigStuff();
}
public async Task DoBigStuffAsync() {
await Task.Run(() => {
DoBigSyncStuff();
});
}
public async void DoFireAndForgetStuffAsync() {
Task.Run(() => {
try {
DoSomeFireAndForgetStuff();
} catch (Exception e) {
// handle your exception
}
});
}
使用该代码,您的执行将是:
DoSomeSyncStuff
DoBigSyncStuff
将开始在DoBigStuffAsync
bigStuff2Task
将开始在DoBigStuff2Async
DoSomeFireAndForgetStuff
将开始异步运行
并且您的代码永远不会知道它是否完成
之后ParentMethodAsync
将等待bigStuffTask
和bigStuff2Task
完成DoAnotherSyncStuffAfterAsyncBigStuff
将(同步)请注意,应明智地使用async void
方法,并且应始终在其中进行自己的异常处理。
否则,如果抛出异常,因为您无法控制执行上下文的时间和内容,最终可能会出现意外和随机的崩溃。
您可以从there获取其他内容,例如youtube livecode(例如this one from Xamarin Evolve,其中James Clancey通过简单的xamarin应用解释线程方面)
我希望它能帮助你实现你想要的目标!