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

问题描述 投票:0回答:3

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

这样做的最佳方法是什么?

谢谢!

c# multithreading iot windows-10-iot-core
3个回答
1
投票

这些线程需要能够读取/写入主线程中的全局变量

处理此要求的最简单方法是删除它。是否可以对解决方案进行编码,以便每个线程拥有一个设备?或者是否可以重新考虑线程的职责,以便它们通过消息传递而不是更新共享数据进行通信?通常这些替代方法会产生更清晰,更少错误的代码。但不总是。

您将需要锁来保护共享数据。最简单的方法是使用lock语句,例如:

object _mutex = new object();
List<Object> MyDevices = null;

...

var device = ...;
lock (_mutex)
{
  MyDevices.Add(device);
}

通常,您希望最小化lock语句中的代码。此外,您可能希望为List<Object>设置一个锁,并为列表中的每个项设置单独的锁,具体取决于您的线程如何使用这些设备。


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。我建议阅读这些以及可能的解决方案。

但是,如果您希望每个线程迭代设备并对每个设备执行某些操作,那么您将遇到问题,因为迭代更改的集合不是一个好主意(并且当使用foreach循环时,将实际抛出例外),所以请记住这一点。


0
投票

其他线程需要立即了解更改

如果您想要低延迟通知,线程必须花费大部分时间睡在某些东西上。例如。执行将等待消息/任务处理的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方法,然后使用异步发布任务或异步方法在调度程序中运行代码。

© www.soinside.com 2019 - 2024. All rights reserved.