【问题标题】:How to sychronously call or wait for an asynchronous callback to complete?如何同步调用或等待异步回调完成?
【发布时间】:2021-07-14 12:49:21
【问题描述】:

这是一个调用“旧库”的代码演示,如果成功,则不返回任何内容,如果错误,则抛出异常。

public void ExampleCallOldCode()
{
    List<string> users = new List<string>() {"user123", "user456"};
    foreach (string userId in users)
    {
        try
        {
            Console.WriteLine($"Processing {userId} started");
            DoSomethingSynchronously(userId);
            Console.WriteLine($"Processing {userId} completed");
        }
        catch (Exception e)
        {
            Console.WriteLine($"Processing {userId} FAILED. {e.Message}");
        }
    }
}

public void DoSomethingSynchronously(string userId)
{
    if (userId.Contains("123"))
        Console.WriteLine($"Doing something with {userId}");
    else
        throw new Exception("UserId needs to contain 123.");
}

我们现在正在升级/集成到一个“新库”,它异步执行工作(在后台使用批处理/排队逻辑)并使用回调来通知成功或失败。

仅供参考:“旧库”是发送电子邮件的包装器。新库是Segment's Analytics.Net package

我不想更改太多旧代码(它在很多地方都使用过)。

  • 如何同步等待新库完成并调用回调函数?
    • 例如我应该使用AutoResetEvents 并致电WaitOne 吗?
    • 还是有更好的选择?
  • 如何处理错误?
  • 是否要创建一个包装器并在失败回调函数中抛出异常?
public void ExampleNewCode()
{
    // Segment initialization and payload setup
    string writeKey = "PqRStUv1WxYzraGHijkA1Pz0AbcDE12F"; 
    Config configWithBatchingOff = new Config().SetAsync(false);
    Segment.Analytics.Initialize(writeKey, configWithBatchingOff);

    Context context = new Context() {{"appName", "MyApp"}};
    Options optionsContext = new Options().SetContext(context);
    Properties properties = new Properties() {{"my_first_prop", "foo bar"}};

    // setup callback handlers
    Analytics.Client.Failed += FailureHandler;
    Analytics.Client.Succeeded += SuccessHandler;
    Logger.Handlers += LogHandler;

    // **** 
    // the old code structure I want to preserve. HOW DO I MAKE THIS WORK ???
    // **** 
    List<string> users = new List<string>() { "user123", "user456" };
    foreach (string userId in users)
    {
        try
        {
            Console.WriteLine($"Processing {userId} started");
            Analytics.Client.Track(userId, "Test Fired", properties, optionsContext);
            Console.WriteLine($"Processing {userId} completed");
        }
        catch (Exception e)
        {
            Console.WriteLine($"Processing {userId} FAILED. {e.Message}");
        }
    }
}

public void FailureHandler(BaseAction action, System.Exception e)
{
    Console.WriteLine($"FailureHandler called for userid={action.UserId}"); 
}
public void SuccessHandler(BaseAction action)
{
    Console.WriteLine($"SuccessHandler called for userid={action.UserId}");
}

【问题讨论】:

    标签: c# asynchronous async-await callback synchronization


    【解决方案1】:

    如果我正确理解了您的问题,那么您可以使用TaskCompletionSource 来解决此问题。

    为了简单起见,我创建了一个虚拟类,它可能会根据提供的参数引发OnSuccess 或OnFailure 事件:

    public class Dummy
    {
        public event EventHandler OnFailure;
        public event EventHandler OnSuccess;
    
        public void DoWork(int i)
        {
            if (i % 2 == 0) OnFailure?.Invoke(this, null);
            else OnSuccess?.Invoke(this, null);
        }
    }
    

    在消费者方面,您可以执行以下操作:

    private static TaskCompletionSource<object> signalling = new TaskCompletionSource<object>();
    public static async Task Main(string[] args)
    {
        Console.WriteLine("Calling new code");
        var dummy = new Dummy();
        dummy.OnSuccess += Dummy_OnSuccess;
        dummy.OnFailure += Dummy_OnFailure;
    
        dummy.DoWork(2);
        try
        {
            await signalling.Task;
            Console.WriteLine("New code has finished");
        }
        catch (Exception)
        {
            Console.WriteLine("New code has failed");
        }
    
        Console.WriteLine("Calling old code");          
    }
    
    private static void Dummy_OnFailure(object sender, EventArgs e)
    {
        Thread.Sleep(1000);
        signalling.TrySetException(new Exception("Operation failed"));
    }
    
    private static void Dummy_OnSuccess(object sender, EventArgs e)
    {
        Thread.Sleep(1000);
        signalling.TrySetResult(null);
    }
    
    • 无论何时发出OnFailure 事件,然后在TaskCompletionSource 上调用TrySetException 以指示操作已在没有运气的情况下完成
    • 每当发出OnSuccess 事件时,我们都会在TaskCompletionSource 上调用TrySetResult 以指示操作已经幸运地完成了
    • 我们是await-ing TaskCompletionSource 这就是为什么我们可以确定在await 行之后请求的操作是成功还是失败

    【讨论】:

    • 谢谢,让我试一试,稍后再回复。我非常感谢示例代码。
    【解决方案2】:

    关于使用分段库,您了解操作(标识、组、跟踪、别名、页面或屏幕)在发送到分段服务器时是成功还是失败的方式是像您一样设置回调处理程序(失败, 成功) 以及 Logger Handler。

    如您上面的示例,成功和失败的回调都通过参数接收 BaseAction,它允许您获得有关成功或失败的操作的信息。

    正如您所提到的,Analytics.NET 库向它发送异步操作,将它们排入队列,当队列已满时,它会继续刷新这些操作并将它们批量发送到分段服务器。因此,如果您想在调用任何操作后自动发送到 Segment 的服务器,您应该调用 Analytics.Client.Flush() 以便获得有关成功或失败回调和 Loggers 处理程序的即时反馈。在您的代码中,它将是:

    public void ExampleNewCode()
    {
        // Segment initialization and payload setup
        string writeKey = "PqRStUv1WxYzraGHijkA1Pz0AbcDE12F"; 
        Config configWithBatchingOff = new Config().SetAsync(false);
        Segment.Analytics.Initialize(writeKey, configWithBatchingOff);
    
        Context context = new Context() {{"appName", "MyApp"}};
        Options optionsContext = new Options().SetContext(context);
        Properties properties = new Properties() {{"my_first_prop", "foo bar"}};
    
        // setup callback handlers
        Analytics.Client.Failed += FailureHandler;
        Analytics.Client.Succeeded += SuccessHandler;
        Logger.Handlers += LogHandler;
    
        List<string> users = new List<string>() { "user123", "user456" };
        foreach (string userId in users)
        {
            try
            {
                Console.WriteLine($"Processing {userId} started");
                Analytics.Client.Track(userId, "Test Fired", properties, optionsContext);
                Analytics.Client.Flush();
                Console.WriteLine($"Processing {userId} completed");
            }
            catch (Exception e)
            {
                Console.WriteLine($"Processing {userId} FAILED. {e.Message}");
            }
        }
    }
    
    public void FailureHandler(BaseAction action, System.Exception e)
    {
        Console.WriteLine($"FailureHandler called for userid={action.UserId}"); 
    }
    public void SuccessHandler(BaseAction action)
    {
        Console.WriteLine($"SuccessHandler called for userid={action.UserId}");
    }
    

    【讨论】:

      猜你喜欢
      • 2021-11-13
      • 1970-01-01
      • 2020-01-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多