想象一下WebForms应用程序,其中有一个名为CreateAll()的主方法。我可以按步骤逐步描述方法任务的过程:
1)存储到数据库(更新/创建Db项目3-4次)
2)启动新主题
3)Result1 =调用soap服务,并使用超时阈值检查状态,并在x分钟后。它继续(状态现在正常,这并不意味着失败)
4)存储到数据库(更新/创建Db项目3-4次)
5)result2 =调用肥皂服务(以火灾和遗忘的方式)
6)更新配置文件(实际上从result1获取)
7)通过使用回调请求,它检查前端的每x秒,结果2的状态,UI显示进度条。如果过程完成(100%),则表示成功
我正在考虑所有这些都是可以按类型分组的任务。基本上有几种类型的操作:
我想为现有实现添加回滚/重试机制,并使用面向任务的体系结构并重构现有的遗留代码。
我发现C#中的 Memento Design Pattern 或命令模式之类的东西可以帮助达到这个目的。我还发现了msdn 重试模式描述有趣。我真的不知道,我希望有人带领我做出最安全和最好的决定......
您能否建议我保留现有实现和流程的最佳方法,但将其包装在一般的抽象重试/回滚/任务列表实现中?
最终实现必须能够在每种情况下重试(无论任何任务或一般故障,例如整个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
答案 0 :(得分:13)
使用Polly查看重试方案,这似乎与您的Pseudo代码完全一致。本答案的最后是文档中的示例。您可以执行各种重试方案,重试和等待等。例如,您可以多次重试一个完整的事务,或者多次重试一组幂等操作,然后在重试时重新编写补偿逻辑政策最终失败了。
memento模式更适用于在文字处理器(Ctrl-Z和Ctrl-Y)中找到的撤消重做逻辑。
要查看的其他有用模式是简单队列,持久队列甚至服务总线,以便为您提供最终的一致性,而无需让用户等待所有内容成功完成。
// Retry three times, calling an action on each retry
// with the current exception and retry count
Policy
.Handle<DivideByZeroException>()
.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<SqlException>()
.WaitAndRetry(3, count =>
{
return TimeSpan.FromSeconds(5);
});
policy.Execute(() => UpdateDatabase1(obj1));
policy.Execute(() => UpdateDatabase2(obj2));
}
答案 1 :(得分:5)
您可以选择Command模式,其中每个命令都包含所有必要的信息,如连接字符串,服务URL,重试计数等。在此之上,您可以考虑rx,数据流块来执行管道。
更新:意图是分离关注。重试逻辑仅限于一个类,它是现有命令的包装器。 您可以进行更多分析,并提供适当的命令,调用者和接收者对象,并添加回滚功能。
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();
}
}
答案 2 :(得分:3)
对我而言,这听起来像是分布式交易&#39;,因为你有不同的资源(数据库,服务通信,文件i / o),并希望进行可能涉及所有这些事务的交易。
在C#中,您可以使用Microsoft Distributed Transaction Coordinator解决此问题。对于每个资源,您需要一个资源管理器。对于数据库,比如sql server和file i / o,据我所知,它已经可用了。对于其他人,你可以自己开发。
例如,要执行这些事务,您可以使用Text="{x:Bind ViewModel.MySuperString}"
类,如下所示:
TransactionScope
(取自here)
的例子要开发自己的资源管理器,您必须实现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();
}
,这可能是一项相当复杂的任务。这是一个简短的example。
答案 3 :(得分:2)
可能有助于您实现目标的一些代码。
public static class Retry
{
public static void Do(
Action action,
TimeSpan retryInterval,
int retryCount = 3)
{
Do<object>(() =>
{
action();
return null;
}, retryInterval, retryCount);
}
public static T Do<T>(
Func<T> action,
TimeSpan retryInterval,
int retryCount = 3)
{
var exceptions = new List<Exception>();
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);
答案 4 :(得分:2)
我会执行所有操作并准备一个反向脚本。如果操作成功,我会清除脚本。否则我会跑它。但这对于潜在的陷阱是开放的,脚本必须准备好处理它们。例如 - 如果在中期有人已经更新了您添加的记录,该怎么办?或根据您的价值计算出汇总数?
仍然:构建一个反向脚本是一个简单的解决方案,那里没有火箭科学。只是
List<Command> reverseScript;
然后,如果你需要回滚:
using (TransactionScope tx= new TransactionScope()) {
foreach(Command cmd in reverseScript) cmd.Execute();
tx.Complete();
}