C# 参与者内部的异步API调用和异常
我知道,但是 所以,我的第一个问题[1]是:这里有没有什么“魔力”,让我们可以同步地等待一个延续中的嵌套任务,而它最终仍然是异步的 当我们处于异步&等待差异时,如何处理故障 让我们创建一个简单的示例:C# 参与者内部的异步API调用和异常,c#,akka.net,C#,Akka.net,我知道,但是 所以,我的第一个问题[1]是:这里有没有什么“魔力”,让我们可以同步地等待一个延续中的嵌套任务,而它最终仍然是异步的 当我们处于异步&等待差异时,如何处理故障 让我们创建一个简单的示例: public static class AsyncOperations { public async static Task<int> CalculateAnswerAsync() { await Task.Delay(1000).ConfigureAw
public static class AsyncOperations
{
public async static Task<int> CalculateAnswerAsync()
{
await Task.Delay(1000).ConfigureAwait(false);
throw new InvalidOperationException("Testing!");
//return 42;
}
public async static Task<string> ConvertAsync(int number)
{
await Task.Delay(600).ConfigureAwait(false);
return number + " :)";
}
}
正如您预期的那样,异常将从第一个操作中冒出
现在,让我们创建一个参与者来处理这些异步操作。为了进行论证,假设CalculateAnswerAsync
和ConvertAsync
应作为一个完整的操作依次使用(例如,类似于StreamWriter.WriteLineAsync
和StreamWriter.FlushAsync
,如果您只想将一行写入流)
公共密封类AsyncTestActor:ReceiveActor
{
公开封条上课
{
}
公共密封类操作结果
{
私有只读字符串消息;
公共操作结果(字符串消息)
{
this.message=消息;
}
公共字符串消息
{
获取{返回消息;}
}
}
公共异步测试器()
{
接收(msg=>
{
AsyncOperations.CalculateAnswerAsync()
.ContinueWith(结果=>
{
变量编号=结果。结果;
var conversionTask=AsyncOperations.conversionAsync(数字);
转换任务。等待(1500);
返回新的操作结果(conversionTask.Result);
})
.派珀托(自我);
});
接收(msg=>Console.WriteLine(“Got”+msg.Message));
}
}
如果没有例外,我仍然得到get 42:)
而没有任何问题,这让我回到了[1]上面的“魔术”点。
另外,示例中提供的AttachedToParent
和ExecuteSynchronously
标志是可选的,还是需要它们才能让一切按预期工作?它们似乎对异常处理没有任何影响
现在,如果CalculateAnswerAsync
抛出一个异常,这意味着result.result
抛出aggregateeexception
,它几乎被吞没了,没有任何痕迹
如果可能的话,我应该在这里做什么,使异步操作中的异常像“常规”异常一样崩溃参与者?TPL中错误处理的乐趣:) 一旦任务开始在它自己的线程上运行,它内部发生的所有事情都已经与调用方异步,包括错误处理
任务
时,该任务在参与者的线程池
上独立运行。这意味着在任务中执行的任何操作都将与参与者异步,因为它运行在不同的线程上。这是在你的文章的顶部。对参与者来说没有什么区别——它只是看起来像一个长期运行的任务
conversionTask.Result
属性将抛出在运行期间捕获的异常,因此您需要在任务
中添加一些错误处理,以确保您的参与者收到出错的通知。请注意,我在这里就是这么做的:-如果你把你的例外变成你的演员可以处理的信息:鸟儿开始歌唱,彩虹闪烁,TPL错误不再是痛苦和痛苦的来源aggregateeexception
将包含封装在其中的内部异常列表-TPL之所以有聚合错误的概念,是因为(a)您有一个任务是聚合中多个任务的延续,即任务。whalll
或(b)您的错误通过链向上传播到父级。您还可以调用AggregateException.flatte()
调用,使管理嵌套异常变得更容易
TPL+Akka.NET的最佳实践
处理来自TPL的异常是一件麻烦事,这是真的-但最好的处理方法是在任务中捕获..
异常,并将它们转换为参与者可以处理的消息类
另外,示例中提供的AttachedToParent和ExecuteSynchronously标志是可选的,还是它们是让一切按预期工作所必需的
当您在continuations上有continuations时,这主要是一个问题-PipeTo
会自动在自身上使用这些标志。它对错误处理没有任何影响,但可以确保您的继续立即在与原始任务
相同的线程上执行
我建议只在你进行大量嵌套的延续时才使用这些标志——一旦你进行了一次以上的延续,TPL就开始自由地安排你的任务(事实上,像OnlyOnCompleted这样的标志在进行了一次以上的延续后就不再被接受了。)只是为了补充Aaron所说的。
截至昨天,在使用任务调度器时,我们确实支持安全异步等待内部参与者
public class AsyncAwaitActor : ReceiveActor
{
public AsyncAwaitActor()
{
Receive<string>(async m =>
{
await Task.Delay(TimeSpan.FromSeconds(1));
Sender.Tell("done");
});
}
}
public class AskerActor : ReceiveActor
{
public AskerActor(ActorRef other)
{
Receive<string>(async m =>
{
var res = await other.Ask(m);
Sender.Tell(res);
});
}
}
public class ActorAsyncAwaitSpec : AkkaSpec
{
[Fact]
public async Task Actors_should_be_able_to_async_await_ask_message_loop()
{
var actor = Sys.ActorOf(Props.Create<AsyncAwaitActor>()
.WithDispatcher("akka.actor.task-dispatcher"),
"Worker");
//IMPORTANT: you must use the akka.actor.task-dispatcher
//otherwise async await is not safe
var asker = Sys.ActorOf(Props.Create(() => new AskerActor(actor))
.WithDispatcher("akka.actor.task-dispatcher"),
"Asker");
var res = await asker.Ask("something");
Assert.Equal("done", res);
}
}
公共类AsyncWaitActor:ReceiveActor
{
公共参与者()
{
接收(异步m=>
{
等待任务延迟(时间跨度从秒(1));
发送人。告知(“完成”);
});
}
}
公共类AskerActor:ReceiveActor
{
公众咨询人(其他行为人)
{
接收(异步m=>
public sealed class AsyncTestActor : ReceiveActor
{
public sealed class Start
{
}
public sealed class OperationResult
{
private readonly string message;
public OperationResult(string message)
{
this.message = message;
}
public string Message
{
get { return message; }
}
}
public AsyncTestActor()
{
Receive<Start>(msg =>
{
AsyncOperations.CalculateAnswerAsync()
.ContinueWith(result =>
{
var number = result.Result;
var conversionTask = AsyncOperations.ConvertAsync(number);
conversionTask.Wait(1500);
return new OperationResult(conversionTask.Result);
})
.PipeTo(Self);
});
Receive<OperationResult>(msg => Console.WriteLine("Got " + msg.Message));
}
}
public class AsyncAwaitActor : ReceiveActor
{
public AsyncAwaitActor()
{
Receive<string>(async m =>
{
await Task.Delay(TimeSpan.FromSeconds(1));
Sender.Tell("done");
});
}
}
public class AskerActor : ReceiveActor
{
public AskerActor(ActorRef other)
{
Receive<string>(async m =>
{
var res = await other.Ask(m);
Sender.Tell(res);
});
}
}
public class ActorAsyncAwaitSpec : AkkaSpec
{
[Fact]
public async Task Actors_should_be_able_to_async_await_ask_message_loop()
{
var actor = Sys.ActorOf(Props.Create<AsyncAwaitActor>()
.WithDispatcher("akka.actor.task-dispatcher"),
"Worker");
//IMPORTANT: you must use the akka.actor.task-dispatcher
//otherwise async await is not safe
var asker = Sys.ActorOf(Props.Create(() => new AskerActor(actor))
.WithDispatcher("akka.actor.task-dispatcher"),
"Asker");
var res = await asker.Ask("something");
Assert.Equal("done", res);
}
}