如何同步调用或等待异步回调完成?

How to sychronously call or wait for an asynchronous callback to complete?

这是调用“旧库”的代码演示,如果成功,returns什么都没有,如果出错,则抛出异常。

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

我们现在 upgrading/integrating 一个“新库”,它异步执行工作(在幕后使用 batching/queuing 逻辑)并使用回调来通知成功或失败。

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

我不想更改太多旧代码(很多地方都用到了)。

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

如果我正确理解你的问题,那么你可以使用 TaskCompletionSource.

来解决这个问题

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

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
  • 行之后是成功还是失败

关于使用段库,当一个动作(Identify、Group、Track、Alias、Page 或 Screen)被发送到 Segment Server 时,您了解它是成功还是失败的方式是像您一样设置回调处理程序(失败,成功)以及记录器处理程序。

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

正如您所提到的,Analytics.NET 库向它发送 Actions 异步将它们排入队列,当队列已满时,它会继续刷新这些动作并将它们成批发送到段服务器。因此,如果您希望在调用任何操作后自动发送到 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}");
}