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

Multi-threading on Windows IoT is causing thread to close

我对为 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();
        });        
    }
}

这是第一个线程的 shell:

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();
                    }
                }
            }
        };
    }
}

这是第二个线程的 shell:

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!");
        }
    }
}

这是第三个线程的 shell:

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).

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

而且我看到了我期望的结果 - 只要我只是 运行宁线程之一。但是一旦我有多个线程 运行 在一起,就会出现问题。

任何方向将不胜感激。

谢谢大家...

根据您的更新代码,尽管由于 I2C 线程中的 while 循环阻止了 运行 方法退出,因此本地 class 变量(网络服务器等)将始终有效。以及 while 循环中 I2C 线程的所有必要变量,以便它们始终有效。但是像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个别地。