在actor和异常中调用异步API

时间:2015-02-16 21:02:20

标签: c# akka.net

我知道PipeTo,但是some stuff, like synchronous waiting on nested continuation, seems to go against the async & await way.

所以,我的第一个问题[1]将是:是否有任何魔法'在这里,我们可以在延续中同步等待嵌套任务,并且它最终仍然是异步的吗?

虽然我们在async& amp;等待差异,如何处理失败?

让我们创建一个简单的例子:

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 + " :)";
    }
}

在&#39;常规&#39;,async&amp;等待方式:

var answer = await AsyncOperations.CalculateAnswerAsync();
var converted = await AsyncOperations.ConvertAsync(answer);

异常会从第一次操作中冒出来,就像你期望的那样。

现在,让我们创建一个与这些异步操作一起工作的角色。为了论证,让我们说CalculateAnswerAsyncConvertAsync应该一个接一个地用作一个完整的操作(类似于StreamWriter.WriteLineAsync和{ {1}}如果您只想将一行写入流中。)

StreamWriter.FlushAsync

如果没有例外,我仍然会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)); } } 没有任何问题,这会让我回到“魔术”中。点以上[1]。 此外,示例中提供的Got 42 :)AttachedToParent标志是可选的,还是几乎要求所有内容都按预期工作?他们似乎对异常处理没有任何影响......

现在,如果ExecuteSynchronously抛出异常,这意味着CalculateAnswerAsync抛出了result.Result,那么它几乎被吞没了。

我应该怎么做,如果可能的话,在异步操作中制作异常会使演员成为常规的&#39;例外吗?

2 个答案:

答案 0 :(得分:10)

TPL中错误处理的乐趣:)

一旦Task开始在自己的线程上运行,其中发生的一切都已经与调用者异步 - 包括错误处理

  1. 当您在演员内部开始第一个Task时,该任务将独立于您的演员的ThreadPool。这意味着你在Task内所做的任何事情都已经与你的演员异步 - 因为它在不同的线程上运行。这篇is why I made a Task.Wait call inside the PipeTo sample you linked to位于您帖子的顶部。对演员没有任何影响 - 它看起来像是一个长期运行的任务。
  2. 例外 - 如果您的内部任务失败,conversionTask.Result属性将抛出在运行期间捕获的异常,因此您希望在Task内添加一些错误处理以确保你的演员被告知出了什么问题。请注意我在这里做到了:https://github.com/petabridge/akkadotnet-code-samples/blob/master/PipeTo/src/PipeTo.App/Actors/HttpDownloaderActor.cs#L117 - 如果你把你的例外变成你的演员可以处理的信息:鸟儿开始唱歌,彩虹闪耀,TPL错误不再是痛苦和痛苦的根源。
  3. 至于抛出异常时会发生什么......
  4.   

    现在,如果CalculateAnswerAsync抛出异常,这意味着   result.Result抛出AggregateException,它几乎被吞噬了   没有踪影。

    AggregateException将包含其中包含的内部异常列表 - TPL具有聚合错误概念的原因是(a)您有一个任务是多个任务的延续总的来说,即Task.WhenAll或(b)您将错误传播到ContinueWith链回到父级。您还可以调用AggregateException.Flatten()调用,以便更轻松地管理嵌套异常。

    TPL + Akka.NET的最佳实践

    处理TPL中的例外是一件令人讨厌的事情,这是真的 - 但解决问题的最佳方式是try..catch..内的Task例外情况并将其转换为邮件类别演员可以处理。

      

    此外,在示例可选中提供了AttachedToParent和ExecuteSynchronously标志,还是他们几乎需要让所有内容按预期工作?

    这主要是一个问题,当你继续延续 - PipeTo自动使用这些标志。它对错误处理没有任何影响,但可确保您的连续在与原始Task相同的线程上立即执行。

    我建议只在你进行大量嵌套延续时才使用这些标志 - 一旦你进行了超过1次延续,TPL开始对如何安排你的任务采取一些自由(实际上,像OnlyOnCompleted这样的标志超过1次继续后不再被接受。)

答案 1 :(得分:7)

只是为了增加亚伦所说的话。 截至昨天,我们确实在使用Task调度程序时支持在actor内部安全的异步等待。

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);
    }
}

这不是我们的默认调度程序,因为它确实带来了性能/吞吐量的代价。 如果触发阻止任务(例如使用task.Wait()task.Result),也存在死锁风险 所以PipeTo模式仍然是首选方法,因为它更适合于actor模型。 但是,如果您真的需要进行一些TPL集成,async await支持就可以作为额外的工具。

此功能实际上使用了PipeTo。 它将使每个任务继续并将其包装在特殊消息中并将该消息传递回actor并在actor自己的并发上下文中执行该任务。