异步服务初始化的竞争条件

本文关键字:竞争 条件 初始化 服务 异步 | 更新日期: 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应用程序时,它是处理异步调用的一个很好的方法。