You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows IoT多线程问题:线程意外终止求助

Troubleshooting Concurrent Threads in Windows IoT Background App

Hey Mike, let's break down what's causing your threads to exit unexpectedly and how to fix it. The core issue is a combination of garbage collection (GC) reclaiming critical objects and misuses of async/await/thread management. Here's your fix plan:

1. Keep Long-Running Objects Alive (Prevent GC)

Right now, your key objects like StreamSocketListener, GpioPin, and I2cDevice are local variables in their respective Start methods. Once those methods finish executing, these variables go out of scope, and GC can clean them up—killing your listeners/connections in the process.

Fix this by:

  • Storing instances of your service classes (TestWebserver, MasterEventListener, I2CEventsListener) as members of StartupTask
  • Moving critical objects (like the listener, GPIO pin, and I2C device) to be members of their respective service classes

2. Fix Async/Await Misuse

async void should only be used for event handlers—using it in your Start methods means the thread pool can't track when the async work completes, leading to premature thread termination. Switch to async Task for better control over async operations.

Also, ThreadPool.RunAsync combined with async void doesn't wait for the async work to finish. Use Task.Run and Task.WhenAll to coordinate your background tasks properly.

3. Optimize I2C Resource Management

Your current I2C code creates and disposes an I2cDevice every 100ms, which is inefficient and can cause resource contention. Reuse a single I2cDevice instance instead. Also, replace Thread.Sleep(100) with await Task.Delay(100) to avoid blocking thread pool threads.


Modified Code Examples

StartupTask.cs

public sealed class StartupTask : IBackgroundTask {
    private BackgroundTaskDeferral _deferral;
    // Hold service instances to prevent GC
    private TestWebserver _webserver;
    private MasterEventListener _masterEventListener;
    private I2CEventsListener _i2cEventListener;

    public async void Run(IBackgroundTaskInstance taskInstance) {
        _deferral = taskInstance.GetDeferral();
        
        // Initialize services
        _webserver = new TestWebserver();
        _masterEventListener = new MasterEventListener();
        _i2cEventListener = new I2CEventsListener();

        // Start all services concurrently
        await Task.WhenAll(
            Task.Run(() => _webserver.Start()),
            Task.Run(() => _masterEventListener.Start()),
            Task.Run(() => _i2cEventListener.Start())
        );

        // Don't call _deferral.Complete() for long-running background tasks
        // This keeps the app alive until it's manually stopped or the device restarts
    }
}

TestWebserver.cs

internal class TestWebserver {
    private const uint BufferSize = 8192;
    private StreamSocketListener _listener; // Keep listener as class member

    public async Task 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();
                uint dataRead = BufferSize;
                while (dataRead == BufferSize) {
                    var result = await input.ReadAsync(buffer, BufferSize, InputStreamOptions.Partial);
                    dataRead = result.Length;
                    request.Append(Encoding.UTF8.GetString(data, 0, (int)dataRead)); // Use actual read length
                }
            }
            using (var output = args.Socket.OutputStream) {
                using (var response = output.AsStreamForWrite()) {
                    string html = "TESTING RESPONSE";
                    var header = $"HTTP/1.1 200 OK\r\nContent-Length: {html.Length}\r\nConnection: close\r\n\r\n";
                    var headerArray = Encoding.UTF8.GetBytes(header);
                    await response.WriteAsync(headerArray, 0, headerArray.Length);
                    var bodyArray = Encoding.UTF8.GetBytes(html);
                    await response.WriteAsync(bodyArray, 0, bodyArray.Length);
                    await response.FlushAsync();
                }
            }
        };
    }
}

MasterEventListener.cs

internal class MasterEventListener {
    private GpioController _gpio;
    private GpioPin _gpioPin; // Keep GPIO pin as class member

    public void Start() {
        _gpio = GpioController.GetDefault();
        if (_gpio == null) {
            Debug.WriteLine("No GPIO controller available");
            return;
        }
        
        _gpioPin = _gpio.OpenPin(4);
        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 isOpen = sender.Read() == GpioPinValue.High;
        Debug.WriteLine(isOpen ? "OPEN!" : "CLOSED!");
    }
}

I2CEventsListener.cs

internal class I2CEventsListener {
    private I2cDevice _i2cDevice; // Reuse I2C device instance

    public async Task Start() {
        string aqs = I2cDevice.GetDeviceSelector();
        DeviceInformationCollection dis = await DeviceInformation.FindAllAsync(aqs);
        
        if (dis.Count == 0) {
            Debug.WriteLine("No I2C devices detected");
            return;
        }

        var settings = new I2cConnectionSettings(3) {
            BusSpeed = I2cBusSpeed.FastMode,
            SharingMode = I2cSharingMode.Shared
        };
        _i2cDevice = await I2cDevice.FromIdAsync(dis[0].Id, settings);

        if (_i2cDevice == null) {
            Debug.WriteLine($"Failed to open I2C device at address {settings.SlaveAddress}");
            return;
        }

        await I2CThreadListener();
    }

    private async Task I2CThreadListener() {
        while (_i2cDevice != null) {
            try {
                byte[] writeBuffer = Encoding.ASCII.GetBytes("000000");
                byte[] readBuffer = new byte[7];
                
                _i2cDevice.Write(writeBuffer);
                _i2cDevice.Read(readBuffer);
                
                var str = Encoding.ASCII.GetString(readBuffer).Trim();
                if (str.Length == 7 && readBuffer[0] > 0) {
                    Debug.WriteLine($"RESULTS! '{str}'");
                }
            } catch (Exception ex) {
                Debug.WriteLine($"I2C Error: {ex.Message}");
                Debug.WriteLine(ex.StackTrace);
            }
            await Task.Delay(100); // Async wait instead of blocking
        }
    }
}

Why This Works

  • Object Lifetime: By holding references to your services and critical objects, you prevent GC from cleaning them up prematurely—this keeps your listeners and connections active indefinitely.
  • Async Control: Using async Task and Task.WhenAll ensures your background tasks are properly tracked, so the thread pool doesn't terminate them early.
  • Resource Efficiency: Reusing the I2C device and using Task.Delay reduces resource contention and keeps your thread pool threads available for other work.

内容的提问来源于stack exchange,提问作者Mike Luken

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:38:32