出于某种奇怪的原因,System.Runtime.Remoting.Messaging.CallContext和AsyncLocal类只能使用完整的CLR。这使得在使用可移植类库或Windows Phone应用程序时进行异步作用非常困难,特别是因为Windows Phone API变得异步;所以我们没有选择不使用async / await。
这实际上意味着在WPF或WinForms中,我们可以编写这样的方法:
private async void button_Click(object sender, EventArgs e)
{
CallContext.LogicalSetData("x", new object());
await Something();
var context = CallContext.LogicalGetData("x");
}
在WPF和WinForms中,框架确保每次单击到同一个按钮都可以获得自己的上下文,并且可以单独运行。使用ThreadLocal<T>
和[ThreadStatic]
很难实现相同的效果,因为每次点击都会在UI线程上执行。
我的问题是,我们如何在Windows Phone,Store和其他不支持CallContext
和AsyncLocal<T>
的应用类型中解决此问题?
一些背景信息:
我们经常希望(业务)逻辑在某种上下文中运行。此上下文可以包含业务逻辑可以在整个操作中使用的信息。在服务器环境中,这很容易想象,因为您需要在数据库事务中运行请求,需要访问当前用户,租户ID等。但即使在客户端应用程序中,操作也可能需要访问上下文信息,例如用于记录的当前运行操作或上下文的相关ID。在此类操作期间(如点击事件),我们可能需要解决其他服务(来自我们的Composition Root)。为了使操作成功,我们可能需要在整个客户端操作中重用相同的组件,这意味着我们的组合根需要知道它正在运行的上下文。
虽然所有这些信息都可以使用服务公共API的方法调用在整个系统中传递,但这不仅会迫使我们通过实现细节污染我们应用程序中的服务的API,这将导致严重维护问题,因为我们必须在整个系统中传递所有这些信息,并且对我们的一个组件的简单内部更改将通过所有方法调用向上传播调用堆栈。当涉及到我们的Composition Root时,我们绝对不希望通过应用程序传递我们的DI库的一些缓存/范围对象,因为这会将我们的业务逻辑紧密地耦合到外部库。显然,我们也不想传递某种service locator。
因此,使用CallContext
或AsyncLocal<T>
等方法实施范围界定在客户端应用程序中非常重要。
答案 0 :(得分:3)
今天没有简单的解决方案,抱歉。当Windows Phone(最终)成为“手机上的Windows 10”(即AsyncLocal<T>
)时,这将是可能的。但是现在......
最简单的方法是明确地(作为参数)或隐式地(作为this
上的成员变量)传递上下文。
也可以使用自定义等待者来实现此限制版本。但除了非常复杂之外,该解决方案还需要您修改整个应用程序中的每个await
(或使用后编译IL重写来实现相同的效果)。