运行 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,以看看这个 属性 是如何使用的。
我有一个 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,以看看这个 属性 是如何使用的。