失败处理
传统的失败处理策略遵循如下模式:
- 对于同步方法调用,调用者检查一个或者多个返回值。被调用的方法使用调用者的执行上下文(通常执行上下文是一个线程)来运行。
- 使用结构化异常处理,调用者将同步方法调用包裹在try/catch/finally语句中,依赖catch{}块的执行来处理错误或者使用catch{}块加上显式的检查被调用方法的返回值。
- 依赖于被调用组件的各种补偿实现的事务,操作系统基础设置和通常很昂贵的机制来跟踪跨越多个线程的上下文。
上述所有方法都不能很容易的用于并发、异步执行,特别是前两种方法根本就不可能用于异步编程。方法在任意的上下文中执行,可能和调用者并行执行。除非有阻塞操作来收集异步操作的结果,阻塞一个在调用者上下文中的线程,失败或者成功都不能很容易的被检测。CLR中的异步编程模型使用Begin/End调用,引入了附加的复杂性,因为附加调用和无论操作结果如何都会被调用到的回调中的异常处理中都需要做失败处理。启动异步操作的代码不是进行失败处理的地方使得代码难以阅读。
CCR分两步解决失败处理的问题:
- 使用Choice和MultipleItemGather仲裁器处理显式或者本地异常。与迭代器合在一起,它们提供了一个类型安全并强壮的方法来处理失败,因为他们强制程序员将成功的情况和失败的情况分别写在两个不同的方法中并且有一个普通的继续(and then also have a common continuation)。例8、13和20分别展示了使用Choice, MultipleItemGather 和在一个迭代器中的Choice来进行显式错误处理(explicit error handling)。
- 隐式或者分布式一场处理被称作Causalities,它允许跨越多个异步操作嵌套和扩展。它与事务共享逻辑上下文概念或者将操作分组并为异步并发环境扩展它。这种机制可以跨越机器边界延伸。
因果关系(Causalities)
Causality是指可以跨多个执行上下文的一串操作,分支或者连接,创建一颗只有一个根的执行逻辑树。这个逻辑分组称为因果关系上下文,隐式的从一个消息的发送者传递到接收者。
Causalities是一种结构化异常机制在多线程环境的扩展。它们允许嵌套,但是依然处理多个并发的一场和与其他的causalities合并(例如因为连接(joins)操作)。
例22.
{
Port<Exception> exceptionPort = new Port<Exception>();
// create a causality using the port instance
Causality exampleCausality = new Causality("root cause", exceptionPort);
// add causality to current thread
Dispatcher.AddCausality(exampleCausality);
// any unhandled exception from this point on, in this method or
// any delegate that executes due to messages from this method,
// will be posted on exceptionPort.
Port<int> portInt = new Port<int>();
Arbiter.Activate(_taskQueue,
Arbiter.Receive(false, portInt, IntHandler)
);
// causalities flow when items are posted or Tasks are scheduled
portInt.Post(0);
// activate a handler on the exceptionPort
// This is the failure handler for the causality
Arbiter.Activate(_taskQueue,
Arbiter.Receive(false, exceptionPort,
delegate(Exception ex)
{
// deal with failure here
Console.WriteLine(ex);
})
);
}
void IntHandler(int i)
{
// print active causalities
foreach (Causality c in Dispatcher.ActiveCausalities)
{
Console.WriteLine(c.Name);
}
// expect DivideByZeroException that CCR will redirect to the causality
int k = 10 / i;
}
在上面的例子中我们演示了一个简单的场景,causalities帮助处理跨多个异步操作的错误。代码的关键步骤如下:
- 创建一个Port<Exception>实例exceptionPort来存储任何从causality上下文中抛出的异常。
- 使用上面创建的异常exceptionPort和一个友好的名称做为构造函数的参数来创建一个新的Causality实例。
- 将这个causality加入到Dispatcher的causalities列表中。
- 一个元素被投递到port,因为这个投递动作发生在causality的上下文中,他将会隐式的“附加”它的causality到被投递的元素。当一个处理器处理这个元素时,附加在元素上的causality将会被添加到执行接收器的线程中。任何接收器中抛出的异常将会被投递到causality中的exceptionPort。
- 一个接收器在exceptionPort上被激活,所以在所有异步操作中,所有的抛出但是没有被处理的异常都会被接收到。
- 因为有元素被投递到portInt实例上,所以处理器被调度,抛出一个DivideByZeroException。CCR调度器自动将这个异常重定向到关联在causality上的exceptionPort。
例23.
{
Port<Exception> exceptionPort = new Port<Exception>();
Causality parentCausality = new Causality(
"Parent",
exceptionPort);
Dispatcher.AddCausality(parentCausality);
Port<int> portInt = new Port<int>();
Arbiter.Activate(_taskQueue,
Arbiter.Receive(false, portInt, IntHandlerNestingLevelZero)
);
portInt.Post(0);
// activate a handler on the exceptionPort
// This is the failure handler for the causality
Arbiter.Activate(_taskQueue,
Arbiter.Receive(false,
exceptionPort,
delegate(Exception ex)
{
// deal with failure here
Console.WriteLine("Parent:" + ex);
})
);
}
void IntHandlerNestingLevelZero(int i)
{
// print active causalities
foreach (Causality c in Dispatcher.ActiveCausalities)
{
Console.WriteLine("Before child is added: " + c.Name);
}
// create new child causality that will nest under existing causality
Port<Exception> exceptionPort = new Port<Exception>();
Causality childCausality = new Causality(
"Child",
exceptionPort);
Dispatcher.AddCausality(childCausality);
Arbiter.Activate(_taskQueue,
Arbiter.Receive(false,
exceptionPort,
delegate(Exception ex)
{
// deal with failure here
Console.WriteLine("Child:" + ex);
})
);
// print active causalities
foreach (Causality c in Dispatcher.ActiveCausalities)
{
Console.WriteLine("After child is added: " + c.Name);
}
// attach a receiver and post to a port
Port<int> portInt = new Port<int>();
Arbiter.Activate(_taskQueue,
Arbiter.Receive(false, portInt, IntHandlerNestingLevelOne)
);
portInt.Post(0);
}
void IntHandlerNestingLevelOne(int i)
{
throw new InvalidOperationException("Testing causality support. Child causality will catch this one");
}
例子的输出:
Before child is added: Parent After child is added: Child Child:System.InvalidOperationException: Testing causality support. Child causality will catch this one at Examples.Examples.IntHandlerNestingLevelOne(Int32 i)
in C:\mri\main\CCR\testsrc\UnitTests\ccruserguideexamples.cs:line 571 at Microsoft.Ccr.Core.Task`1.Execute()
in C:\mri\main\CCR\src\Core\Templates\GeneratedFiles\Task\Task01.cs:line 301 at Microsoft.Ccr.Core.TaskExecutionWorker.ExecuteTaskHelper(ITask currentTask)
in C:\mri\main\CCR\src\Core\scheduler_roundrobin.cs:line 1476 at Microsoft.Ccr.Core.TaskExecutionWorker.ExecuteTask(ITask& currentTask, DispatcherQueue p)
in C:\mri\main\CCR\src\Core\scheduler_roundrobin.cs:line 1376 at Microsoft.Ccr.Core.TaskExecutionWorker.ExecutionLoop()
in C:\mri\main\CCR\src\Core\scheduler_roundrobin.cs:line 1307
在上面的例子中,我们演示了一个嵌套的异步序列:
- NestedCausalityExample方法向一个带接收器的port中加入了一个causality
- IntHandlerNestingLevelZero方法与NestedCausalityExample方法并行异步执行,向
连接和因果关系(Joins and Causalities)
CCR的因果关系实现了一个独一无二的功能:它可以组合从两个不同的执行路径来的因果关系。类似于结构化异常,但是推广到并发的情况。因果关系可以嵌套,给程序员在内部外部因果关系中处理异常或者显式的投递到父因果关系中。
例24.
{
Port<int> intPort = new Port<int>();
Port<int> leftPort = new Port<int>();
Port<string> rightPort = new Port<string>();
Port<Exception> leftExceptionPort = new Port<Exception>();
Port<Exception> rightExceptionPort = new Port<Exception>();
// post twice so two handlers run
intPort.Post(0);
intPort.Post(1);
// activate two handlers that will execute concurrently and create
// two different parallel causalities
Arbiter.Activate(_taskQueue,
Arbiter.Receive(false, intPort,
delegate(int i)
{
Causality leftCausality = new Causality("left", leftExceptionPort);
Dispatcher.AddCausality(leftCausality);
// post item on leftPort under the context of the left causality
leftPort.Post(i);
})
);
Arbiter.Activate(_taskQueue,
Arbiter.Receive(false, intPort,
delegate(int i)
{
Causality rightCausality = new Causality("right", rightExceptionPort);
Dispatcher.AddCausality(rightCausality);
// post item on rightPort under the context of the right causality
rightPort.Post(i.ToString());
})
);
// activate one join receiver that executes when items are available on
// both leftPort and rightPort
Arbiter.Activate(_taskQueue,
Arbiter.JoinedReceive<int, string>(false, leftPort, rightPort,
delegate(int i, string s)
{
throw new InvalidOperationException("This exception will propagate to two peer causalities");
})
);
// activate a handler on the exceptionPort
// This is the failure handler for the causality
Arbiter.Activate(_taskQueue,
Arbiter.Receive(false, leftExceptionPort,
delegate(Exception ex)
{
// deal with failure here
Console.WriteLine("Left causality: " + ex);
})
);
// activate a handler on the exceptionPort
// This is the failure handler for the causality
Arbiter.Activate(_taskQueue,
Arbiter.Receive(false, rightExceptionPort,
delegate(Exception ex)
{
// deal with failure here
Console.WriteLine("Right causality: " + ex);
})
);
}
例子的输出:
Left causality: System.InvalidOperationException: This exception will propagate to two peer causalities Right causality: System.InvalidOperationException: This exception will propagate to two peer causalities
为了可读性,上面的例子中大量使用了匿名方法来保持所有的逻辑都在一个方法中。这个例子演示了:
- 两个独立的处理器创建了两个causalities(分别叫做:"left"和"right")
- 向每个port中投递一个消息
- 由于在两个port(leftPort和rightPort)上的连接条件被满足,所以第三个处理器被执行
- 连接处理器在两个causalities的上下文中抛出一个异常
关键点是:当连接处理器(join handler)执行并且抛出异常,两个peer causalities被激活并且都独立的得到了投递到他们所负责的exception port上的异常。
重要:由于causalities使用常规的CCR ports来接收异常,你可以使用CCR协调原语来组合跨多个causalitites的异常处理。Joins,interleave和choice全都是合适的而且强大的方式来组合跨多个并发,多层异步操作的异常处理。
posted on 2007-12-17 11:23 iceboundrock 阅读(721) 评论(0) 编辑 收藏 举报