如何从主线程中的全局变量读/写

时间:2019-04-25 21:12:55

标签: c# multithreading iot windows-10-iot-core

我有一个已创建的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”交互,因此,如果一个线程对该列表进行了更改,则其他线程需要立即知道更改。

执行此操作的最佳方法是什么?

谢谢!

3 个答案:

答案 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方法,然后使用异步发布的任务或异步方法在调度程序中运行代码。