异步服务初始化的竞争条件
本文关键字:竞争 条件 初始化 服务 异步 | 更新日期: 2023-09-27 18:13:15
我在c# WPF专有框架(本质上是Caliburn Micro和Castle Windsor的混合)中工作,我有两个具有竞争条件的单例模块:
DeviceService
-管理与发送数据的物理设备的连接的服务。该服务是"可启动的",因此会自动异步构造和初始化。
ConnectionIndicatorViewModel
-一个客户端ViewModel,主要负责向用户传递DeviceService
所管理的连接的状态。改变状态主要基于DeviceService
触发的事件。
我的问题在于应用程序启动。在ViewModel的构造函数中,我将默认状态设置为"Pending",因为我假设服务还没有完成初始化。然后ViewModel简单地处理由Service触发的"Initialized"事件。在这个处理程序中,我通过服务上的属性来评估实际的连接状态,并更新ViewModel。
现在,所有这些都工作得很好,因为竞态条件极不可能出现。然而,在不太可能的情况下,服务在ViewModel被构造之前完成了初始化,它将永远不会处理"Initialized"事件,而只是停留在"Pending"状态。
我考虑过修改Service接口以返回属性的可等待类型,这样任何试图访问属性的模块都必须等待初始化完成,但我不确定这是否是最好的方法。我也很警惕让部分客户端启动服务,因为如果几个模块使用它,谁应该初始化它?
是否有一些常规的方法来处理这种异步初始化,我错过了?
您提到使用事件来完成服务和ViewModel之间的通信,您可以使用响应式扩展(Reactive Extensions, Rx)而不是使用事件,这有能力消除您上面描述的竞争条件。
简单地说,这将服务从拉模型转变为推模型,它将通过流推出数据事件,并允许您在流上编写LINQ查询。如果你不熟悉Rx,这里有很多有用的信息。
在这个使用Rx的场景中,我将让服务公开IObservable<T>
的属性,其中T是您的类型(我猜是某种状态枚举),该属性的后备字段是重要的部分,这将是一个大小为1的ReplaySubject<T>
。这意味着每当有人"订阅"属性时,他们将收到发布到主题的最后一个值。因此,这意味着在发布和订阅流之间不存在竞争条件。
这可能在代码中更容易理解:
public enum State
{
Initializing,
Initialized,
}
public interface IMyService
{
IObservable<State> Status { get; }
}
public class MyService : IMyService
{
private ReplaySubject<State> _state;
public MyService()
{
_state = new ReplaySubject<State>(1);
_state.OnNext(State.Initializing);
// Do initialisation stuff
_state.OnNext(State.Initialized);
}
public IObservable<State> Status { get { return _state; } }
}
这个例子只说明在当前线程上初始化服务(即同步),这意味着它会阻塞调用线程,我猜这将是Dispatcher线程,如果这是一个基于XAML的应用程序。
如果你需要异步完成初始化,你可以使用Observable.Create<T>
或Observable.Start<T>
在后台线程上启动工作,这样它就不会阻塞调度器(UI)线程。
要使用这个服务,你可以这样做:
public class MyViewModel
{
private State _state;
public MyViewModel(IMyService myService)
{
myService.Status.ObserveOn(DispatcherScheduler.Current)
.Subscribe(x =>
{
_state = x;
});
}
public bool IsReady { get { return _state == State.Initialized; } }
}
现在在Service和ViewModel之间没有竞争条件了。
关于响应式扩展可能有很多需要学习的地方,但是当你在实现MVVM应用程序时,它是处理异步调用的一个很好的方法。