我有一个已创建的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”交互,因此,如果一个线程对该列表进行了更改,则其他线程需要立即知道更改。
执行此操作的最佳方法是什么?
谢谢!
答案 0 :(得分:1)
这些线程需要能够读取/写入主线程中的全局变量
处理此要求的最简单方法是将其删除。是否可以对解决方案进行编码,以使每个线程都拥有一个设备?还是有可能重新考虑线程的职责,以便它们通过消息传递而不是更新共享数据进行通信?通常,这些替代方法可以使代码更简洁,错误更少。但并非总是如此。
您将需要锁来保护共享数据。最简单的方法是使用lock
语句,例如:
object _mutex = new object();
List<Object> MyDevices = null;
...
var device = ...;
lock (_mutex)
{
MyDevices.Add(device);
}
通常,您希望最小化lock
语句中的代码。另外,您可能希望为List<Object>
拥有一个锁,并为列表中的每个 item 拥有一个单独的锁,具体取决于线程如何使用这些设备。
答案 1 :(得分:0)
您可能要考虑使用的一件事是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。我建议您阅读该书以及可能的解决方案。
但是,如果您打算让每个线程在设备上进行迭代并对每个线程做某事,那么您将遇到问题,因为在变化的集合上进行迭代不是一个好主意(并且在使用{{1 }}循环,实际上会引发异常),因此也请记住这一点。
答案 2 :(得分:0)
其他线程需要立即了解更改
如果您想要低延迟通知,则线程必须将大部分时间都花在某个事物上。例如。正在执行Dispatcher.Run()
,它将等待消息/任务处理。
在这种情况下,您可以使用ObservableCollection
代替List,然后编写CollectionChanged
处理程序以转发3个线程的通知。或者,如果您想要的是,如果您不希望启动更改的线程处理更改事件,则将通知转发给除当前线程之外的其他2个线程。
我不确定Dispatcher
类在Windows IoT平台上是否可用。绝对不是.NET core。即使没有,也可以使用创建一个的高级构建块。这是一个示例实现,它也实现了同步上下文,非常简单,因为它依赖于高级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还是我的自定义变量,所有线程都必须调用它的Run方法,然后使用异步发布的任务或异步方法在调度程序中运行代码。