我有一个我创建的C#Windows IoT后台应用程序。该应用程序在ThreadPool中有多个线程无限期运行。
这些线程需要能够读取/写入主线程中的全局变量,但我不知道如何实现这一点。这是我想要做的一个例子:
// main task
public sealed class StartupTask : IBackgroundTask
{
private static BackgroundTaskDeferral _Deferral = null;
private static MyThreadClass1 thread1 = null;
private static MyThreadClass2 thread2 = null;
private static MyThreadClass3 thread3 = null;
List<Object> MyDevices = null;
public async void Run(IBackgroundTaskInstance taskInstance)
{
_Deferral = taskInstance.GetDeferral();
MyDevices = GetDeviceList();
thread1 = new MyThreadClass1();
await ThreadPool.RunAsync(workItem =>
{
thread1.Start();
});
thread2 = new MyThreadClass2();
await ThreadPool.RunAsync(workItem =>
{
thread2.Start();
});
thread3 = new MyThreadClass3();
await ThreadPool.RunAsync(workItem =>
{
thread3.Start();
});
}
}
internal class MyThreadClass1
{
public async void Start()
{ }
}
internal class MyThreadClass2
{
public async void Start()
{ }
}
internal class MyThreadClass3
{
public async void Start()
{ }
}
在正在运行的三个线程中的任何一个中,我都需要能够读取和写入List<Object> MyDevices
。
线程都有不同的功能,但它们都与“MyDevices”交互,因此如果一个线程对该列表进行了更改,其他线程需要立即知道更改。
这样做的最佳方法是什么?
谢谢!
这些线程需要能够读取/写入主线程中的全局变量
处理此要求的最简单方法是删除它。是否可以对解决方案进行编码,以便每个线程拥有一个设备?或者是否可以重新考虑线程的职责,以便它们通过消息传递而不是更新共享数据进行通信?通常这些替代方法会产生更清晰,更少错误的代码。但不总是。
您将需要锁来保护共享数据。最简单的方法是使用lock
语句,例如:
object _mutex = new object();
List<Object> MyDevices = null;
...
var device = ...;
lock (_mutex)
{
MyDevices.Add(device);
}
通常,您希望最小化lock
语句中的代码。此外,您可能希望为List<Object>
设置一个锁,并为列表中的每个项设置单独的锁,具体取决于您的线程如何使用这些设备。
您可能想要考虑使用的一件事是ObservableCollection
。此类实现INotifyPropertyChanged
接口,该接口通知任何侦听器对底层集合的更改。
接下来,您将要在PropertyChanged
类中为Thread
实现一个事件处理程序(我建议创建一个接口或基类来处理这个,因为您似乎为每个Thread
使用不同的类):
public sealed class MyThreadBase
{
private ObservableCollection<object> MyDevices;
public MyThreadBase(ObservableCollection<object> deviceList)
{
MyDevices = deviceList;
MyDevices.PropertyChanged += MyDevices_PropertyChanged; // Register listener
}
private void MyDevices_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
lock (MyDevices)
{
// Do something with the data...
}
}
}
使用lock
语句,以便在另一个线程正在读取或写入MyDevices
时阻塞线程。这在同步中通常很重要,被称为readers-writers problem。我建议阅读这些以及可能的解决方案。
但是,如果您希望每个线程迭代设备并对每个设备执行某些操作,那么您将遇到问题,因为迭代更改的集合不是一个好主意(并且当使用foreach
循环时,将实际抛出例外),所以请记住这一点。
其他线程需要立即了解更改
如果您想要低延迟通知,线程必须花费大部分时间睡在某些东西上。例如。执行将等待消息/任务处理的Dispatcher.Run()
。
如果这是你的情况,你可以使用ObservableCollection
而不是List,并编写CollectionChanged
处理程序,为你的3个线程转发通知。或者,如果这是你想要的,如果你不希望发起更改的线程处理更改的事件,则将通知转发给2个其他线程,不包括当前线程。
我不确定Dispatcher
类是否可用于Windows IoT平台。绝对不是.NET核心的情况。即使没有,也可以使用高级构建块来创建一个。这是一个实现同步上下文的示例实现,非常简单,因为它依赖于高级ConcurrentQueue和BlockingCollection泛型类。
using kvp = KeyValuePair<SendOrPostCallback, object>;
enum eShutdownReason : byte
{
Completed,
Failed,
Unexpected,
}
class Dispatcher : IDisposable
{
const int maxQueueLength = 100;
readonly ConcurrentQueue<kvp> m_queue;
readonly BlockingCollection<kvp> m_block;
public Dispatcher()
{
m_queue = new ConcurrentQueue<kvp>();
m_block = new BlockingCollection<kvp>( m_queue, maxQueueLength );
createdThreadId = Thread.CurrentThread.ManagedThreadId;
prevContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext( new SyncContext( this ) );
}
readonly SynchronizationContext prevContext;
readonly int createdThreadId;
class SyncContext : SynchronizationContext
{
readonly Dispatcher dispatcher;
public SyncContext( Dispatcher dispatcher )
{
this.dispatcher = dispatcher;
}
// https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/
public override void Post( SendOrPostCallback cb, object state )
{
dispatcher.Post( cb, state );
}
}
/// <summary>Run the dispatcher. Must be called on the same thread that constructed the object.</summary>
public eShutdownReason Run()
{
Debug.Assert( Thread.CurrentThread.ManagedThreadId == createdThreadId );
while( true )
{
kvp h;
try
{
h = m_block.Take();
}
catch( Exception ex )
{
ex.logError( "Dispatcher crashed" );
return eShutdownReason.Unexpected;
}
if( null == h.Key )
return (eShutdownReason)h.Value;
try
{
h.Key( h.Value );
}
catch( Exception ex )
{
ex.logError( "Exception in Dispatcher.Run" );
}
}
}
/// <summary>Signal dispatcher to shut down. Can be called from any thread.</summary>
public void Stop( eShutdownReason why )
{
Logger.Info( "Shutting down, because {0}", why );
Post( null, why );
}
/// <summary>Post a callback to the queue. Can be called from any thread.</summary>
public void Post( SendOrPostCallback cb, object state = null )
{
if( !m_block.TryAdd( new kvp( cb, state ) ) )
throw new ApplicationException( "Unable to post a callback to the dispatcher: the dispatcher queue is full" );
}
void IDisposable.Dispose()
{
Debug.Assert( Thread.CurrentThread.ManagedThreadId == createdThreadId );
SynchronizationContext.SetSynchronizationContext( prevContext );
}
}
无论您是使用内置Dispatcher还是自定义Dispatcher,所有线程都必须调用它的Run方法,然后使用异步发布任务或异步方法在调度程序中运行代码。