如何同步调用或等待异步回调完成?
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
我不想更改太多旧代码(很多地方都用到了)。
- 如何同步等待新库完成并调用回调函数?
- 例如我应该使用
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}");
}
如果我正确理解你的问题,那么你可以使用 TaskCompletionSource
.
来解决这个问题
为了简单起见,我创建了一个虚拟 class,它可能根据提供的参数引发 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
行之后是成功还是失败
关于使用段库,当一个动作(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}");
}
这是调用“旧库”的代码演示,如果成功,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
我不想更改太多旧代码(很多地方都用到了)。
- 如何同步等待新库完成并调用回调函数?
- 例如我应该使用
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}");
}
如果我正确理解你的问题,那么你可以使用 TaskCompletionSource
.
为了简单起见,我创建了一个虚拟 class,它可能根据提供的参数引发 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
-ingTaskCompletionSource
这就是为什么我们可以确定请求的操作在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}");
}