假设您有两种方法:DoJob1()
,DoJob2()
。
它们中的每一个都具有类似事务的行为,即执行其工作或报告错误。
我应该如何编写一个执行DoJob1()
然后DoJob2()
的方法,但它本身就像事务一样,也就是说,保证DoJob1()
执行的操作的回滚以防万一处理DoJob2()
时出错?
当然,您可以自由选择错误处理方式(bool返回值,实例异常,全局错误变量 - 您可以命名)。
这个想法是写(某些)类似事务的方法。当发生异常时,建议用户重复“交易”。
我想到了问题的可能解决方法,我将在一段时间内发布; (为了不限制你的想象力)
答案 0 :(得分:1)
一般来说,我这样做:
transactionalJob1()
transaction_begin()
doJob1()
transaction_end()
exception:
log
transaction_rollback()
transactionalJob2()
transaction_begin()
doJob2()
transaction_end()
exception:
log
transaction_rollback()
transactionalJob1And2()
transaction_begin()
doJob1()
doJob2()
transaction_end()
exception:
transaction_rollback()
如果您选择的语言支持模板方法,您可以将其全部包装起来。
答案 1 :(得分:0)
我有同样的想法。
这是一种beta方法(C#):
class Program
{
Random rnd = new Random();
void DoJob1()
{
if (rnd.NextDouble() <= 0.5)
throw new ArgumentException("First case");
Console.WriteLine("First job done.");
}
void DoJob1Rollback()
{
Console.WriteLine("First job rollback.");
}
void DoJob2()
{
if (rnd.NextDouble() <= 0.5)
throw new ArgumentException("Second case");
Console.WriteLine("Second job done.");
}
void DoJob2Rollback()
{
Console.WriteLine("Second job rollback.");
}
void Run()
{
bool success = false;
while (true)
{
try
{
TransactHelper.DoTransaction(
DoJob1,
DoJob2
);
success = true;
}
catch (Exception ex)
{
Console.WriteLine("--------------------------------------");
Console.WriteLine("Exception: " + ex.Message);
// Console.WriteLine("Stack trace:");
// Console.WriteLine(ex.StackTrace);
// Console.WriteLine();
}
if (!success)
{
// ask the user for another chance
Console.Write("Retry? ");
if (Console.ReadLine().ToLower() != "yes")
break;
}
else
break;
}
Console.WriteLine("Batch job {0}", success ? "succeeded." : "did not succeed.");
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
}
static void Main(string[] args)
{
(new Program()).Run();
}
}
这看起来很不错,就像运行TransactHelper.DoTransaction
一样class TransactHelper
{
public static void DoTransaction(params ThreadStart[] actions)
{
int i = 0;
int n = actions.Length;
// exception to pass on
Exception ret_ex = null;
// do the list of jobs
for (; i < n; ++i)
{
try
{
ThreadStart ts = actions[i];
ts();
}
catch (Exception ex) // register exception
{
ret_ex = ex;
break;
}
}
if (ret_ex != null) // exception registered, rollback what's done
{
int k = i; // method which failed
for (; i >= 0; --i)
{
MethodInfo mi = actions[i].Method;
string RollbackName = mi.Name + "Rollback";
// set binding flags - the same as the method being called
BindingFlags flags = (mi.IsStatic) ? BindingFlags.Static : BindingFlags.Instance;
if (mi.IsPrivate)
flags |= BindingFlags.NonPublic;
if (mi.IsPublic)
flags |= BindingFlags.Public;
// call rollback
MethodInfo miRollback = mi.DeclaringType.GetMethod(RollbackName, flags);
miRollback.Invoke(actions[i].Target, new object[] { });
}
throw new TransactionException("Transaction failed on method " + actions[k].Method.Name, ret_ex);
}
}
}
[global::System.Serializable]
public class TransactionException : Exception
{
public TransactionException() { }
public TransactionException(string message) : base(message) { }
public TransactionException(string message, Exception inner) : base(message, inner) { }
protected TransactionException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base(info, context) { }
}
然而,存在陷阱。
我最初考虑过这个问题,因为我在一个产生大量代码的团队中工作,如:
_field = new Field();
_field.A = 1;
_filed.B = "xxx";
而不是
Field tmp = new Field();
tmp.A = 1;
tmp.B = "xxx";
_field = tmp;
交易的美妙之处在于:
也许我正试图重新发明轮子? 是否有一个更明智的方法库? 我没有预见到这种设计的缺陷是什么?
答案 2 :(得分:0)
我采用的第二种方法是在单独的类中记录回滚操作。
class Transaction
{
IList<Action> _rollBacks = new List<Action>();
public void AddRollBack(Action action)
{
_rollBacks.Add(action);
}
public void Clear()
{
_rollBacks.Clear();
}
public void RollBack()
{
for (int i = _rollBacks.Count - 1; i >= 0; --i)
{
_rollBacks[i]();
_rollBacks.RemoveAt(i);
}
}
}
实际代码是成对编写的,例如:
File.Move(file, file + "__");
string s = (string) file.Clone();
tr.AddRollBack(() => File.Move(s + "__", s));
当我需要做某事事务性时,我创建了一个事务对象,并将敏感代码覆盖到try ... catch ... finally
Transaction tr = new Transaction();
UpdateLogic ul = new UpdateLogic();
ul.Transaction = tr;
// ........ more initialization (of classes)
try
{
// .... more initialization, which is a part of transaction
ul.DoPreparation();
ul.DoCopying();
tr.Clear(); // at this point most of update is ok
ul.DoCleanup();
ShowMessage("Update completed", "Update completed successfully.", false);
}
catch (Exception ex)
{
// handel error
}
finally
{
// show message in UI
try
{
tr.RollBack();
}
catch (Exception ex)
{
ShowMessage("Error while performing rollback of actions", ex.Message, true);
}
// ... update is done
}
非常方便,因为这可以保证文件的完整性。通过明智的编码,即使进程在执行操作或回滚期间被终止(但在文件系统中留下一些垃圾),也可以保持完整性。
答案 3 :(得分:0)
这取决于该方法应该做什么样的工作。通常,首先将事务的副作用存储到临时位置,然后当事务提交时,将副作用存储到一个原子操作中的永久位置。根据您是否正在修改文件系统中的某些内容(从一些书中读取数据库事务日志如何工作),一些内存数据结构,网络上的内容或其他内容,有关如何执行此操作的详细信息非常不同。 / p>
例如,我曾经编写了一个事务性内存中键值数据库(http://dimdwarf.sourceforge.net/的一部分)。它保留了在事务期间完成的所有修改的临时列表。然后,当事务提交时,修改的密钥被锁定在数据库中,修改存储在数据库中(此操作不得失败),之后密钥被解锁。一次只能提交一个事务,在事务完全提交之前,没有其他事务可以看到更改。