如何实现Serial COM端口写入的异步函数或同步互斥机制?
Hey there! I see you're running into an issue where two functions trying to write to the serial port at the same time are throwing exceptions—totally makes sense, since serial ports are single-threaded resources that can't handle concurrent writes. Let's walk through two solid solutions to fix this.
Root Cause
Your OnTimedEvent (triggered by the timer) and GetFileNames functions are both calling ComWrite independently. When they fire at the same time, they're trying to access the serial port simultaneously, which causes the conflict. We need to make sure only one write operation happens at a time.
Solution 1: Use a lock Statement (Simple Synchronization)
The easiest way to handle this is with a lock object that ensures only one thread can execute the serial write code at any given moment.
First, add a private lock object to your form class:
// Add this at the top of your form class (outside any method) private readonly object _serialWriteLock = new object();
Then modify your ComWrite method to wrap the write operation in the lock:
private void ComWrite(string msg) { // Only one thread can enter this block at a time lock (_serialWriteLock) { // Always check if the port is open before writing to avoid extra exceptions if (ComPort.IsOpen) { ComPort.Write(msg); } } }
How this works: When either OnTimedEvent or GetFileNames calls ComWrite, the lock will force the second caller to wait until the first write operation is fully completed before executing its own write. No more concurrent access conflicts!
Solution 2: Use an Asynchronous Write Queue (Better for High-Volume Writes)
If you're dealing with lots of data (like the full list of .wav files), a lock might block your UI thread temporarily. A better approach here is to queue all write requests and process them one at a time in the background.
Add these private fields to your form class:
private readonly ConcurrentQueue<string> _writeQueue = new ConcurrentQueue<string>(); private bool _isProcessingQueue = false;
Then create a method to process the queue asynchronously:
private async Task ProcessWriteQueueAsync() { // Don't start a new processing loop if one is already running if (_isProcessingQueue) return; _isProcessingQueue = true; while (_writeQueue.TryDequeue(out var message)) { if (ComPort.IsOpen) { try { // Use WriteAsync to avoid blocking the UI thread var byteData = Encoding.ASCII.GetBytes(message); await ComPort.WriteAsync(byteData, 0, byteData.Length); } catch (Exception ex) { MessageBox.Show($"Failed to write to serial port: {ex.Message}"); } } } _isProcessingQueue = false; }
Update your ComWrite method to add messages to the queue instead of writing directly:
private void ComWrite(string msg) { // Add the message to the queue _writeQueue.Enqueue(msg); // Start processing the queue (no need to await here) _ = ProcessWriteQueueAsync(); }
This approach ensures all write requests are handled in order, and since it's asynchronous, your UI will stay responsive even when sending large lists of files.
Extra Tips to Avoid Headaches
- Check Port State First: Always verify
ComPort.IsOpenbefore attempting writes—this cuts down on unnecessary exceptions. - Clean Up When Closing the Port: When closing the serial port, make sure the write queue is finished processing to avoid leftover messages causing errors:
else if (btnPortState.Text == "COMPort Open / Click To Close") { btnPortState.Text = "COMPort Closed / Click to Open"; btnPortState.BackColor = Color.Firebrick; // Wait for the queue to finish processing (optional but recommended) while (_isProcessingQueue) { Application.DoEvents(); // Keep UI responsive System.Threading.Thread.Sleep(50); } ComPort.Close(); } - Handle Exceptions Gracefully: Wrap your write operations in try/catch blocks to give users clear feedback instead of unhandled exceptions.
Both solutions will fix your concurrent write issue—pick the one that fits your use case best! The lock is great for simple scenarios, while the queue is better for heavy write loads.
内容的提问来源于stack exchange,提问作者James Morrish

