哪个是在C#中为同步/异步任务添加重试/回滚机制的最佳方法?

想象一下WebForms应用程序,其中有一个名为CreateAll()的主方法。 我可以一步一步地描述方法任务的过程如下:

1)存储到数据库(更新/创建Db项目3-4次)

2)启动一个新线程

3)Result1 =调用soap服务,并使用超时阈值检查状态和x分钟后。它继续(状态现在正常,这并不意味着失败)

4)存储到数据库(更新/创建Db项目3-4次)

5)result2 =呼叫肥皂服务(以火灾和遗忘的方式)

6)更新配置文件(实际上从result1获取)

7)通过使用回调请求,它检查前端的每x秒结果2的状态,UI显示进度条。如果过程完成(100%),则表示成功

我正在考虑所有这些都是可以按类型分组的任务。基本上,几种类型的操作是:

  • Type1:数据库事务
  • Type2:服务通信/交易
  • Type3:配置文件I / O事务

我想为现有实现添加回滚/重试机制,并使用面向任务的体系结构并重构现有的遗留代码。

我发现C#中的Memento Design PatternCommand Pattern之类的东西可以帮助达到这个目的。我还发现msdn Retry Pattern描述很有趣。 我不知道,我希望有人带领我做出最安全和最好的决定……

您是否可以建议我保留现有实现和流程的最佳方法,但将其包装在一般的抽象重试/回滚/任务列表实现中?

最终的实现必须能够在每种情况下重试(无论任何任务或一般故障,例如整个createAll进程中的超时等),并且还会有一个回滚决策列表,其中应用程序必须能够回滚已完成的所有任务。

我想要一些例子来解决这个耦合代码。


PseudoCode可能有用:

class something { static result CreateAll(object1 obj1, object2 obj2 ...) { //Save to database obj1 //... //Update to database obj1 // //NEW THREAD //Start a new thread with obj1, obj2 ...CreateAll //... } void CreateAllAsync() { //Type1 Save to database obj1 //... //Type1 Update to database obj2 //Type2 Call Web Service to create obj1 on the service (not async) while (state != null && now < times) { if (status == "OK") break; else //Wait for X seconds } //Check status continue or general failure //Type1 Update to database obj2 and obj1 //Type2 Call Web Service to create obj2 on the service (fire and forget) //Type3 Update Configuration File //Type1 Update to database obj2 and obj1 //.. return; } //Then the UI takes the responsibility to check the status of result2 

看看使用Polly进行重试场景,这似乎与您的Pseudo代码完全一致。 在这个答案的最后是文档中的一个示例。 您可以执行各种重试方案,重试和等待等。例如,您可以多次重试完整的事务,或者多次重试一组幂等操作,然后在重试时重新编写补偿逻辑政策终于失败了。

对于在文字处理器(Ctrl-Z和Ctrl-Y)中找到的撤消重做逻辑,更多的纪念模式。

要查看的其他有用模式是简单队列,持久队列甚至服务总线,以便为您提供最终的一致性,而无需让用户等待所有内容成功完成。

 // Retry three times, calling an action on each retry // with the current exception and retry count Policy .Handle() .Retry(3, (exception, retryCount) => { // do something }); 

基于伪代码的示例可能如下所示:

 static bool CreateAll(object1 obj1, object2 obj2) { // Policy to retry 3 times, waiting 5 seconds between retries. var policy = Policy .Handle() .WaitAndRetry(3, count => { return TimeSpan.FromSeconds(5); }); policy.Execute(() => UpdateDatabase1(obj1)); policy.Execute(() => UpdateDatabase2(obj2)); } 

您可以选择Command模式,其中每个命令都包含所有必要的信息,如连接字符串,服务URL,重试计数等。在此之上,您可以考虑使用rx ,数据流块来执行管道。

高层次的观点 在此处输入图像描述

更新:意图是分离关注。 重试逻辑仅限于一个类,它是现有命令的包装器。 您可以进行更多分析,并提供适当的命令,调用者和接收者对象,并添加回滚function。

 public abstract class BaseCommand { public abstract RxObservables Execute(); } public class DBCommand : BaseCommand { public override RxObservables Execute() { return new RxObservables(); } } public class WebServiceCommand : BaseCommand { public override RxObservables Execute() { return new RxObservables(); } } public class ReTryCommand : BaseCommand // Decorator to existing db/web command { private readonly BaseCommand _baseCommand; public RetryCommand(BaseCommand baseCommand) { _baseCommand = baseCommand } public override RxObservables Execute() { try { //retry using Polly or Custom return _baseCommand.Execute(); } catch (Exception) { throw; } } } public class TaskDispatcher { private readonly BaseCommand _baseCommand; public TaskDispatcher(BaseCommand baseCommand) { _baseCommand = baseCommand; } public RxObservables ExecuteTask() { return _baseCommand.Execute(); } } public class Orchestrator { public void Orchestrate() { var taskDispatcherForDb = new TaskDispatcher(new ReTryCommand(new DBCommand)); var taskDispatcherForWeb = new TaskDispatcher(new ReTryCommand(new WebCommand)); var dbResultStream = taskDispatcherForDb.ExecuteTask(); var WebResultStream = taskDispatcherForDb.ExecuteTask(); } } 

对我来说,这听起来像’分布式事务’,因为你有不同的资源(数据库,服务通信,文件i / o),并希望进行可能涉及所有这些事务的事务。

在C#中,您可以使用Microsoft分布式事务处理协调器解决此问题。 对于每个资源,您需要一个资源管理器。 对于数据库,比如sql server和file i / o,据我所知,它已经可用了。 对于其他人,你可以发展自己的。

例如,要执行这些事务,您可以使用TransactionScope类,如下所示:

 using (TransactionScope ts = new TransactionScope()) { //all db code here // if an error occurs jump out of the using block and it will dispose and rollback ts.Complete(); } 

(从这里取的例子)

要开发自己的资源管理器,您必须实现IEnlistmentNotification ,这可能是一项相当复杂的任务。 这是一个简短的例子 。

一些代码可以帮助您实现目标。

 public static class Retry { public static void Do( Action action, TimeSpan retryInterval, int retryCount = 3) { Do(() => { action(); return null; }, retryInterval, retryCount); } public static T Do( Func action, TimeSpan retryInterval, int retryCount = 3) { var exceptions = new List(); for (int retry = 0; retry < retryCount; retry++) { try { if (retry > 0) Thread.Sleep(retryInterval); return action(); } catch (Exception ex) { exceptions.Add(ex); } } throw new AggregateException(exceptions); } } 

呼叫并重试如下:

 int result = Retry.Do(SomeFunctionWhichReturnsInt, TimeSpan.FromSeconds(1), 4); 

参考: http : //gist.github.com/KennyBu/ac56371b1666a949daf8

嗯……听起来真的很糟糕。 你不能打开一个交易,写一些东西到数据库,然后在公园里遛狗。 因为交易有这种令人讨厌的习惯,即为每个人锁定资源。 这消除了您的最佳选择:分布式事务。

我会执行所有操作并准备一个反向脚本。 如果操作成功,我会清除脚本。 否则我会跑它。 但这对于潜在的陷阱是开放的,脚本必须准备好处理它们。 例如 – 如果在中期有人已更新您添加的记录,该怎么办? 或根据您的价值计算出汇总数?

仍然:建立一个反向脚本是一个简单的解决方案,那里没有火箭科学。 只是

 List reverseScript; 

然后,如果你需要回滚:

 using (TransactionScope tx= new TransactionScope()) { foreach(Command cmd in reverseScript) cmd.Execute(); tx.Complete(); }