在 PCL 和 Windows Phone 应用程序中实现异步作用域
Implementing asynchronous scoping in PCL and Windows Phone applications
由于某些奇怪的原因,System.Runtime.Remoting.Messaging.CallContext and AsyncLocal 类 都只能使用完整的 CLR。这使得在使用便携式 Class 库或 Windows Phone 应用程序时很难进行异步范围界定,尤其是因为 Windows Phone APIs正在变得只异步;所以我们真的没有选择不使用 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
和 [= 的应用程序类型中解决这个问题17=]?
一些背景资料:
我们经常希望(业务)逻辑在某种上下文中 运行。此上下文可以包含业务逻辑可以在整个操作过程中使用的信息。在服务器环境中这真的很容易想象,因为你需要在数据库事务中 运行 请求,需要访问当前用户,租户 id 等。但即使在客户端应用程序中,操作也可能需要访问上下文信息,例如当前 运行ning 操作的相关 ID 或日志记录的上下文。在此类操作(如点击事件)期间,我们可能需要解析其他服务(来自我们的 Composition Root)。为了操作成功,我们可能需要在整个客户端操作中重复使用相同的组件,这意味着我们的组合根需要知道它是 运行ning.
的上下文
虽然所有这些信息都可以通过服务的方法调用传递到整个系统public API,但这不仅会迫使我们污染 API我们的应用程序中的服务具有实现细节,这将导致严重的维护问题,因为我们必须在整个系统中传递所有这些信息,并且对我们的一个组件进行简单的内部更改将通过所有的调用堆栈向上传播方法调用。当涉及到我们的组合根时,我们绝对不想通过应用程序传递我们的 DI 库的某些 cache/scope 对象,因为这会将我们的业务逻辑紧密地耦合到外部库。显然,我们也不想传递某种 service locator.
因此,在客户端应用程序中,使用 CallContext
或 AsyncLocal<T>
之类的东西来实现范围界定非常重要。
抱歉,今天没有简单的解决方案。当 Windows Phone(最终)变为 "Windows 10 on a phone"(即 AsyncLocal<T>
)时,这将成为可能。但是现在...
最简单的方法是显式(作为参数)或隐式(作为 this
上的成员变量)传递上下文。
也可以使用自定义等待程序实现此功能的有限版本。但除了非常复杂之外,该解决方案还需要您修改整个应用程序中的每个 await
(或使用 post-compilation IL 重写来实现相同的效果)。
由于某些奇怪的原因,System.Runtime.Remoting.Messaging.CallContext and AsyncLocal 类 都只能使用完整的 CLR。这使得在使用便携式 Class 库或 Windows Phone 应用程序时很难进行异步范围界定,尤其是因为 Windows Phone APIs正在变得只异步;所以我们真的没有选择不使用 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
和 [= 的应用程序类型中解决这个问题17=]?
一些背景资料:
我们经常希望(业务)逻辑在某种上下文中 运行。此上下文可以包含业务逻辑可以在整个操作过程中使用的信息。在服务器环境中这真的很容易想象,因为你需要在数据库事务中 运行 请求,需要访问当前用户,租户 id 等。但即使在客户端应用程序中,操作也可能需要访问上下文信息,例如当前 运行ning 操作的相关 ID 或日志记录的上下文。在此类操作(如点击事件)期间,我们可能需要解析其他服务(来自我们的 Composition Root)。为了操作成功,我们可能需要在整个客户端操作中重复使用相同的组件,这意味着我们的组合根需要知道它是 运行ning.
的上下文虽然所有这些信息都可以通过服务的方法调用传递到整个系统public API,但这不仅会迫使我们污染 API我们的应用程序中的服务具有实现细节,这将导致严重的维护问题,因为我们必须在整个系统中传递所有这些信息,并且对我们的一个组件进行简单的内部更改将通过所有的调用堆栈向上传播方法调用。当涉及到我们的组合根时,我们绝对不想通过应用程序传递我们的 DI 库的某些 cache/scope 对象,因为这会将我们的业务逻辑紧密地耦合到外部库。显然,我们也不想传递某种 service locator.
因此,在客户端应用程序中,使用 CallContext
或 AsyncLocal<T>
之类的东西来实现范围界定非常重要。
抱歉,今天没有简单的解决方案。当 Windows Phone(最终)变为 "Windows 10 on a phone"(即 AsyncLocal<T>
)时,这将成为可能。但是现在...
最简单的方法是显式(作为参数)或隐式(作为 this
上的成员变量)传递上下文。
也可以使用自定义等待程序实现此功能的有限版本。但除了非常复杂之外,该解决方案还需要您修改整个应用程序中的每个 await
(或使用 post-compilation IL 重写来实现相同的效果)。