Windows IoT 上的多线程导致线程关闭

Posted

技术标签:

【中文标题】Windows IoT 上的多线程导致线程关闭【英文标题】:Multi-threading on Windows IoT is causing thread to close 【发布时间】:2019-09-09 10:50:30 【问题描述】:

我对为 Windows IoT 编写应用程序比较陌生。我有一个 Windows IoT 后台应用程序,我想从主线程生成三个单独的线程。 (我希望它们都在单独的后台线程中运行的原因是因为它们会做的一些工作可能很耗时,所以我显然不想阻止任何东西)。

第一个线程正在运行一个小型网络服务器。

第二个线程正在侦听 Raspberry PI 上的 GPIO 引脚。

第三个线程是通过I2C监听设备。

出于某种原因,我似乎无法让所有三个线程都保持打开状态。这是我的 StartupTask 代码:

public sealed class StartupTask : IBackgroundTask

    private static BackgroundTaskDeferral _Deferral = null;
    public async void Run(IBackgroundTaskInstance taskInstance)
    
        _Deferral = taskInstance.GetDeferral();

        // do some stuff on the main thread here...

        // thread 1
        var webserver = new TestWebserver();
        await ThreadPool.RunAsync(workItem =>
        
            webserver.Start();
        );

        // thread 2
        var masterEventListener = new MasterEventListener();
        await ThreadPool.RunAsync(workItem =>
        
            masterEventListener.Start();
        );

        // thread 3
        var i2cEventListener = new I2CEventListener();
        await ThreadPool.RunAsync(workItem =>
        
            i2cEventListener.Start();
        );        
    

这是第一个线程的外壳:

internal class TestWebserver

    private const uint BufferSize = 8192;
    public async void Start()
    
        var listener = new StreamSocketListener();
        await listener.BindServiceNameAsync(8081);

        listener.ConnectionReceived += async (sender, args) =>
        
            var request = new StringBuilder();
            using (var input = args.Socket.InputStream)
            
                var data = new byte[BufferSize];
                IBuffer buffer = data.AsBuffer();
                var dataRead = BufferSize;

                while (dataRead == BufferSize)
                
                    await input.ReadAsync(buffer, BufferSize, InputStreamOptions.Partial);
                    request.Append(Encoding.UTF8.GetString(data, 0, data.Length));
                    dataRead = buffer.Length;
                
            

            using (var output = args.Socket.OutputStream)
            
                using (var response = output.AsStreamForWrite())
                
                    string html = "TESTING RESPONSE";
                    using (var bodyStream = new MemoryStream(html))
                    
                        var header = $"HTTP/1.1 200 OK\r\nContent-Length: bodyStream.Length\r\n\r\nConnection: close\r\n\r\n";
                        var headerArray = Encoding.UTF8.GetBytes(header);
                        await response.WriteAsync(headerArray, 0, headerArray.Length);
                        await bodyStream.CopyToAsync(response);
                        await response.FlushAsync();
                    
                
            
        ;
    

这是第二个线程的外壳:

internal class MasterEventListener

    public void Start()
    
        GpioController gpio = GpioController.GetDefault();
        GpioPin gpioPin = gpio.OpenPin(4); // pin4

        if (gpioPin.IsDriveModeSupported(GpioPinDriveMode.InputPullUp))
        
            gpioPin.SetDriveMode(GpioPinDriveMode.InputPullUp);
        
        else
        
            gpioPin.SetDriveMode(GpioPinDriveMode.Input);
        

        gpioPin.Write(GpioPinValue.High);
        gpioPin.DebounceTimeout = TimeSpan.FromMilliseconds(25);
        gpioPin.ValueChanged += Pin_ValueChanged;
    

    private void Pin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
    
        bool value = sender.Read() == GpioPinValue.High;

        if (value)
        
            Debug.WriteLine("OPEN!");
        
        else
        
            Debug.WriteLine("CLOSED!");
        
    

这是第三个线程的外壳:

internal class I2CEventsListener

    public async void Start()
    
        string aqs = I2cDevice.GetDeviceSelector();
        DeviceInformationCollection dis = await DeviceInformation.FindAllAsync(aqs);

        I2CThreadListener(dis);
    

    private async void I2CThreadListener(DeviceInformationCollection dis)
    
        while(true)
        
            var settings = new I2cConnectionSettings(3); // I2C address 3
            settings.BusSpeed = I2cBusSpeed.FastMode;
            settings.SharingMode = I2cSharingMode.Shared;

            using (I2cDevice device = await I2cDevice.FromIdAsync(dis[0].Id, settings))
            
                if (device != null)
                
                    try
                    
                        byte[] writeBuffer = Encoding.ASCII.GetBytes("000000");
                        byte[] readBuffer = new byte[7];

                        device.Write(writeBuffer);
                        device.Read(readBuffer);

                        var str = Encoding.Default.GetString(readBuffer);
                        if (str != null && str.Trim().Length == 7 && Convert.ToInt32(readBuffer[0]) > 0)
                        
                            Debug.WriteLine("RESULTS! '" + str + "'");
                        
                    
                    catch (Exception ex)
                    
                        Debug.WriteLine(ex.Message);
                        Debug.WriteLine(ex.StackTrace);
                    
                
            
            Thread.Sleep(100);
                       
    );

如果我注释掉这两个线程中的任何一个,剩下的线程将无限期地运行并完美运行。

如果我注释掉一个线程,剩下的两个线程(有时)完美运行大约 30 秒,然后其中一个线程将终止并显示如下消息:

The thread 0xad0 has exited with code 0 (0x0).

我的日志中从未收到任何错误消息,因此我认为不会引发任何类型的错误。

我看到了我期望的结果——只要我只运行一个线程。但是一旦我有多个线程一起运行,就会导致问题。

任何方向都将不胜感激。

谢谢大家...

【问题讨论】:

可能是因为异常。它是否在调试控制台上显示异常消息? 但显然你在询问之前必须自己检查过。 没有异常被击中。我认为这与我如何设置线程有关?我对整个 async/await 概念有点陌生,所以这根本不会让我感到震惊! 您的代码看起来过于复杂了。您可以使用 Task.Run(async () => // your forever loop that needs to run in background ) 从 Main 方法中生成两个任务 @Mike 如果没有异常,您可以检查它是否挂在任何地方。以及如何确定“线程 0xe80”是 I2C 的第二个线程? 【参考方案1】:

根据您的更新代码,虽然由于 I2C 线程中的 while 循环阻止了 Run 方法退出,因此本地类变量(网络服务器等)将始终有效。并且所有必要的 I2C 线程变量都在 while 循环中,以便它们始终有效。但是像listenergpiogpioPin这样的局部变量将被GC收集,然后在方法执行完成后不再有效。然后线程将退出。

为了解决这个问题,我对您的代码进行了一些编辑,它可以正常工作。你可以试试:

public sealed class StartupTask : IBackgroundTask
    
        private static BackgroundTaskDeferral _Deferral = null;
        private static TestWebserver webserver = null;
        private static MasterEventListener masterEventListener = null;
        private static I2CEventsListener i2cEventListener = null;

        public async void Run(IBackgroundTaskInstance taskInstance)
        
            _Deferral = taskInstance.GetDeferral();

            // do some stuff on the main thread here...

            // thread 1
            webserver = new TestWebserver();
            await Windows.System.Threading.ThreadPool.RunAsync(workItem =>
            
                webserver.Start();
            );

            // thread 2
            masterEventListener = new MasterEventListener();
            await Windows.System.Threading.ThreadPool.RunAsync(workItem =>
            
                masterEventListener.Start();
            );

            // thread 3
            i2cEventListener = new I2CEventsListener();
            await Windows.System.Threading.ThreadPool.RunAsync(workItem =>
            
                i2cEventListener.Start();
            );

            Debug.WriteLine("The Run method exit...");
        

        internal class TestWebserver
        
            private StreamSocketListener listener = null;
            private const uint BufferSize = 8192;
            public async void Start()
            
                listener = new StreamSocketListener();
                await listener.BindServiceNameAsync("8081");

                listener.ConnectionReceived += async (sender, args) =>
                
                    var request = new StringBuilder();
                    using (var input = args.Socket.InputStream)
                    
                        var data = new byte[BufferSize];
                        IBuffer buffer = data.AsBuffer();
                        var dataRead = BufferSize;

                        while (dataRead == BufferSize)
                        
                            await input.ReadAsync(buffer, BufferSize, InputStreamOptions.Partial);
                            request.Append(Encoding.UTF8.GetString(data, 0, data.Length));
                            dataRead = buffer.Length;
                        
                    

                    using (var output = args.Socket.OutputStream)
                    
                        using (var response = output.AsStreamForWrite())
                        
                            string html = "TESTING RESPONSE";
                            using (var bodyStream = new MemoryStream(Encoding.ASCII.GetBytes(html)))
                            
                                var header = $"HTTP/1.1 200 OK\r\nContent-Length: bodyStream.Length\r\n\r\nConnection: close\r\n\r\n";
                                var headerArray = Encoding.UTF8.GetBytes(header);
                                await response.WriteAsync(headerArray, 0, headerArray.Length);
                                await bodyStream.CopyToAsync(response);
                                await response.FlushAsync();
                            
                        
                    
                ;
            
        

        internal class MasterEventListener
        
            private GpioController gpio = null;
            private GpioPin gpioPin = null;

            public void Start()
            

                gpio = GpioController.GetDefault();
                gpioPin = gpio.OpenPin(4); // pin4

                if (gpioPin.IsDriveModeSupported(GpioPinDriveMode.InputPullUp))
                
                    gpioPin.SetDriveMode(GpioPinDriveMode.InputPullUp);
                
                else
                
                    gpioPin.SetDriveMode(GpioPinDriveMode.Input);
                

                gpioPin.Write(GpioPinValue.High);
                gpioPin.DebounceTimeout = TimeSpan.FromMilliseconds(25);
                gpioPin.ValueChanged += Pin_ValueChanged;
            

            private void Pin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
            
                bool value = sender.Read() == GpioPinValue.High;

                if (value)
                
                    Debug.WriteLine("OPEN!");
                
                else
                
                    Debug.WriteLine("CLOSED!");
                
            
        

        internal class I2CEventsListener
        
            public async void Start()
            
                string aqs = I2cDevice.GetDeviceSelector();
                DeviceInformationCollection dis = await DeviceInformation.FindAllAsync(aqs);

                I2CThreadListener(dis);
            

            private async void I2CThreadListener(DeviceInformationCollection dis)
            
                var settings = new I2cConnectionSettings(3); // I2C address 3
                settings.BusSpeed = I2cBusSpeed.FastMode;
                settings.SharingMode = I2cSharingMode.Shared;

                I2cDevice device = await I2cDevice.FromIdAsync(dis[0].Id, settings);
                if (null == device)
                
                    Debug.WriteLine("Get I2C device is NULL. Exiting...");
                

                byte[] writeBuffer = Encoding.ASCII.GetBytes("000000");
                byte[] readBuffer = new byte[7];

                while (true)
                
                    try
                    
                        device.Write(writeBuffer);
                        device.Read(readBuffer);

                        var str = Encoding.Default.GetString(readBuffer);
                        if (str != null && str.Trim().Length == 7 && Convert.ToInt32(readBuffer[0]) > 0)
                        
                            Debug.WriteLine("RESULTS! '" + str + "'");
                        
                    
                    catch (Exception ex)
                    
                        Debug.WriteLine(ex.Message);
                        Debug.WriteLine(ex.StackTrace);
                    

                    Thread.Sleep(100);
                
            
        


    

建议创建短期的工作项以使用线程池。参考“Best practices for using the thread pool”。

对于长时间运行任务,对于您的情况,如果您的三个线程没有相互通信,您可以为每个任务单独创建一个background application。

【讨论】:

谢谢!我永远不会猜到这是局部变量的垃圾收集,但你是对的。这解决了我的问题!不确定我自己是否能解决这个问题,非常感谢您抽出宝贵时间!!!

以上是关于Windows IoT 上的多线程导致线程关闭的主要内容,如果未能解决你的问题,请参考以下文章

Python 多线程效率不高吗

DELPHI下的多线程程序设计

powershell Windows上的多线程大文件夹删除

Qt学习 之 多线程程序设计

进程和线程

QT 多线程程序设计(也有不少例子)