运行 C# 中线程不安全的周期性异步函数

Run periodically and asynchronously a function that is thread unsafe in C#

我有一个 IP 摄像机,它带有自己的库。 它的方法都是同步的,它们看起来像这样。

这是为了不存在竞争条件,即我无法在 Configure() 运行ning 时调用 GetImage()。 请注意,其中一些方法需要几秒钟才能完成。

public interface ICamera
{
    bool Configure(object args); // Takes a long time
    Image GetImage(); // Takes a little time but must not be accessed by other thread.
}

现在,我想将这个库包装在一个服务 class 中,该服务将在新框架准备就绪时启动一个事件。 事件订阅者将负责更新 ui.

我想做类似的事情(伪c#)

public class ICameraService
{
    public event EventHandler NewFrameAvailable;
    public event EventHandler StateChanged;
    
    private ICamera camera;
    private Timer frameTimer;

    public Configure() {
         StateChanged?.Invoke(this, StateChangedEventArgs("Configuring"));

         // No other camera methods must be called while this is running
         // I could maybe use lock(camera)?
         camera.Configure(...);
         StateChanged?.Invoke(this, StateChangedEventArgs("Idle"));
    }
    
    public void StartCapture() {
       frameTimer.Tick += () => {
           var image = camera.Capture();
           image = DoSomeBasicProcessing(image);
           NewFrameAvailable?.Invoke(this, NewFrameAvailableEventArgs(image));
       };

       // Sets the timer interval to 25 fps.
       frameTimer.Interval = 1.0 / 25.0;

       StateChanged?.Invoke(this, StateChangedEventArgs("Capturing"));
       frameTimer.Start();
    }

    public void StopCapture() {
       frameTimer.Stop();
       StateChanged?.Invoke(this, StateChangedEventArgs("Idle"));
    }
}

如何确保无论调用线程如何,所有 ICamera 的方法 运行 在单个后台线程上按顺序进行?

我走在正确的轨道上吗?我不明白我是否应该使用任务,锁定每个相机访问权限(但为每个帧锁定一个对象似乎非常昂贵)。 解决这个我认为并不罕见的问题的最实用方法是什么?

我也有这些问题:

Tick 在哪个线程上 运行启用 lambda(它在调用者的线程上吗?每次调用都在不同的线程上吗?我希望它在一个线程中。)

我想了解如何构建 ICameraService,以便所有线程不安全代码都没有竞争条件,但仍向 ViewModel 提供一个异步接口(使用事件或 async/await),它将调用 CameraService。

.NET 计时器 class 具有一项功能,可提供周期性事件并锁定特定对象。参见 Microsoft's example

它是在线程池中使用你自己的锁的语法糖。它不会保证每个间隔事件都在同一个线程上 运行,但会保证每个事件一次处理一个。

如果您想更好地控制或保证在单个线程上 运行ning,我会 create a thread and use a Stopwatch 确定设置下一个线程超时所用的时间。

只需启动一个初始化相机并定期运行捕获的线程,直到您调用停止相机。不要忘记,NewFrameAvailable 在捕获线程上运行,因此要么创建一个副本,要么您可以计算处理所用时间的 WaitOne(timeout)。

你可以这样做:

public class ICameraService
{
    public event EventHandler NewFrameAvailable;
    public event EventHandler StateChanged;
    
    private ICamera camera;
    private Timer frameTimer;
    private ManualResetEvent _terminate = new AutoResetEvent(false);
    private int interval;

    private Thread thread;

    public void StartCapture(int interval)
    {
        _terminate.Reset();
        this.interval = interval;
        thread = new Thread(ThreadMethod);
    }

    public void ThreadMethod()
    {
        StateChanged?.Invoke(this, StateChangedEventArgs("Configuring"));
        camera.Configure(...);
        StateChanged?.Invoke(this, StateChangedEventArgs("Capturing"));
        while(!_terminate.WaitOne(interval))
        {
            var image = camera.Capture();
            image = DoSomeBasicProcessing(image);
            NewFrameAvailable?.Invoke(this, NewFrameAvailableEventArgs(image));
        }
        StateChanged?.Invoke(this, StateChangedEventArgs("Idle"));
    }

    public void StopCapture() 
    {
       _terminate.Set();
       thread.Join();
       StateChanged?.Invoke(this, StateChangedEventArgs("Idle"));
    }
}

这是同一主题的变体,使用 TPL classes 而不是线程原语:

public class CameraService
{
    public event EventHandler NewFrameAvailable;
    public event EventHandler StateChanged;

    private ICamera _camera;
    private CancellationTokenSource _cts;
    private Task _task;

    public void StartCapture(TimeSpan interval)
    {
        _cts = new CancellationTokenSource();
        _task = Task.Factory.StartNew(() =>
        {
            StateChanged?.Invoke(this, new StateChangedEventArgs("Configuring"));
            _camera.Configure(/* ... */);
            StateChanged?.Invoke(this, new StateChangedEventArgs("Capturing"));
            while (true)
            {
                var delayTask = Task.Delay(interval, _cts.Token);
                var image = _camera.Capture();
                image = DoSomeBasicProcessing(image);
                NewFrameAvailable?.Invoke(this, new NewFrameEventArgs(image));
                try { delayTask.GetAwaiter().GetResult(); }
                catch (OperationCanceledException) { break; }
            }
            StateChanged?.Invoke(this, new StateChangedEventArgs("Idle"));
        }, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
    }

    public void StopCapture()
    {
        _cts.Cancel();
        try { _task.GetAwaiter().GetResult(); }
        catch (OperationCanceledException) { } // Ignore
        _cts.Dispose();
        StateChanged?.Invoke(this, StateChangedEventArgs("Idle"));
    }
}

如果您希望 CameraService class 的事件在 UI 线程上引发(可选),您可以考虑模仿 SynchronizingObject property of the System.Timers.Timer class. You could study the source code of this class here,以看看这个 属性 是如何使用的。