C# DLL非阻塞Scan方法实现咨询:方案合理性与异常处理
Hey there! Let's break down your current implementation, its issues, and how to fix them step by step.
1. Key Drawbacks of Your Current Approach
- Async responsibility shifted to the caller: Your
Scanmethod is inherently blocking, and you’re forcing the caller to wrap it in aTaskto avoid locking up the main thread. This breaks encapsulation—your DLL should handle asynchronous execution internally so callers don’t have to remember this (and risk blocking the UI if they forget). - UI thread safety risk: The
ReaderEventis raised from the backgroundTaskthread. WhileMessageBox.Showmight work here, any other UI operations (like updating a text box or button state) in the event handler will throw a cross-thread exception, since UI controls can only be accessed from the thread they were created on. - Unobserved exceptions: The
Taskstarted withTask.Factory.StartNewhas no exception handling. If your blocking scan code throws an error, this exception will go unobserved—older .NET versions crash, newer versions silently swallow it, making debugging a nightmare. - Invalid disposal: Your
Readerclass doesn’t implementIDisposable, but you’re callingreader.Dispose()in the event handler. This will either fail to compile or throw a runtime error—you need to properly implement the disposable pattern if your class holds unmanaged resources.
2. How to Catch Exceptions from the DLL
Since your scan runs in a background Task, you have reliable ways to catch errors:
- Use
awaitwithtry/catch(recommended): Switch toTask.Run(safer thanTask.Factory.StartNewfor thread pool work) and wrap the call in a try block:private async void bButton_Click(object sender, EventArgs e) { reader = new Reader(); reader.ReaderEvent += reader_EventChanged; try { await Task.Run(() => reader.Scan(SelectedReader)); } catch (Exception ex) { MessageBox.Show($"Scan failed: {ex.Message}"); // Clean up resources here } } - Handle exceptions via
ContinueWith: If you can’t useawait, attach a continuation to the task to handle errors, and ensure it runs on the UI thread:polling = Task.Factory.StartNew(() => reader.Scan(SelectedReader)) .ContinueWith(task => { if (task.Exception != null) { MessageBox.Show($"Scan failed: {task.Exception.InnerException.Message}"); } }, TaskScheduler.FromCurrentSynchronizationContext());
3. Conceptual Errors in Your Code
- Missing event null check: If no one subscribes to
ReaderEvent, calling it will throw aNullReferenceException. Fix this with:// Option 1: Safe invoke with null check var handler = ReaderEvent; if (handler != null) { handler(this, alertEventArgs); // Pass 'this' as sender, not new object() } // Option 2: C# 6+ null-conditional operator ReaderEvent?.Invoke(this, alertEventArgs); - Redundant
AlertEventArgscode: You don’t need manual private fields—use auto-implemented properties to clean up the class:public class AlertEventArgs : EventArgs { public string uuiData { get; set; } public uint cardStateData { get; set; } } - Incorrect
Taskcreation:Task.Factory.StartNewis low-level and can behave unexpectedly with scheduling.Task.Runis the preferred way to offload work to the thread pool in .NET 4.5+. - No cancellation support: Your implementation doesn’t let callers cancel an in-progress scan, which is a common usability gap.
4. Async/Await Isn’t Just "Simplified Task"—It Solves Real Problems
You’re right that async/await builds on Task, but it fixes critical gaps in your current approach:
- Automatic thread context restoration: When you
awaita task in a UI thread, code afterawaitautomatically runs back on the UI thread—no more cross-thread exceptions for UI operations. - Cleaner error handling:
try/catchworks naturally withawait, making exception handling straightforward. - Readable flow: Async code reads like synchronous code, so it’s easier to follow and maintain.
Refactored DLL with Async/Await Example
Here’s how to rewrite your Reader class to handle async internally, making it safer and easier for callers:
public class Reader : IDisposable { public event EventHandler<AlertEventArgs> ReaderEvent; // Async scan method that doesn't block the caller public async Task ScanAsync(string readerName) { var alertEventArgs = new AlertEventArgs(); alertEventArgs.uuiData = null; // Offload blocking scan code to the thread pool await Task.Run(() => { // Your blocking scan logic here if (ScanFinished) { alertEventArgs.uuiData = "Scan Finished!"; } alertEventArgs.cardStateData = readerState[0].eventState; }); // Raise event on the caller's original thread (e.g., UI thread) ReaderEvent?.Invoke(this, alertEventArgs); } // Proper IDisposable implementation for resource cleanup public void Dispose() { // Clean up unmanaged resources here (if any) GC.SuppressFinalize(this); } }
Caller Code with Async/Await
private async void bButton_Click(object sender, EventArgs e) { using (var reader = new Reader()) { reader.ReaderEvent += reader_EventChanged; try { await reader.ScanAsync(SelectedReader); } catch (Exception ex) { MessageBox.Show($"Scan failed: {ex.Message}"); } } } void reader_EventChanged(object sender, AlertEventArgs e) { // Runs safely on the UI thread—no cross-thread issues! MessageBox.Show($"{e.uuiData} Estado: {e.cardStateData.ToString("X")}"); }
This version fixes all the issues in your original code: it encapsulates async logic, ensures UI thread safety, supports proper exception handling, and uses modern C# practices.
内容的提问来源于stack exchange,提问作者Joster
相关产品推荐
相关产品推荐

