如何在WinForms项目中优雅地跨类传递CancellationToken以取消成员资格同步长耗时操作
Alright, let's tackle this cancellation requirement step by step. The core goal is to pass a cancellation signal from MembershipSynchronizationService all the way down to the long-running PopulateMembership method, so the operation can terminate gracefully when a stop request comes in. Here's the structured approach:
Core Approach
- Add a
CancellationTokenSourceinMembershipSynchronizationServiceto manage cancellation state - Pass the associated
CancellationTokenthrough the call chain (Class1 → Class2 → Class3) - Modify the long-running method in Class3 to periodically check for cancellation requests
- Trigger cancellation in the
Disposemethod ofMembershipSynchronizationService - Handle cancellation exceptions gracefully to avoid unhandled errors and clean up resources
Step-by-Step Code Modifications
1. Update Class1: MembershipSynchronizationService
We'll add a CancellationTokenSource to track cancellation, pass the token to the synchronizer, and trigger cancellation during disposal.
public sealed class MembershipSynchronizationService : IDisposable { private readonly object _sync = new object(); private CancellationTokenSource _cts; // Tracks active cancellation requests /// <summary> /// Performs a single run of the synchronization service. /// </summary> public void RunOnce() { lock (_sync) { // Reset cancellation source if a previous run was canceled _cts?.Dispose(); _cts = new CancellationTokenSource(); var synchronizer = new MembershipSynchronizer(MembershipManagerUnsecured.SoleInstance.MembershipManager); try { synchronizer.Synchronize(_cts.Token); } catch (OperationCanceledException) { // Log cancellation event gracefully LoggingUtility.LogTrace(LoggingCategory.Membership, "Synchronization operation was canceled."); } finally { _cts.Dispose(); _cts = null; } } } #region Dispose pattern ~MembershipSynchronizationService() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposing) { lock (_sync) { // Trigger cancellation if a synchronization run is in progress _cts?.Cancel(); _cts?.Dispose(); _cts = null; } } } #endregion }
2. Update Class2: MembershipSynchronizer
Modify the Synchronize method to accept a CancellationToken and pass it directly to the PopulateMembership method.
internal sealed class MembershipSynchronizer { private readonly IMembershipPopulatorFactory _membershipPopulatorFactory; private readonly MembershipManager _membershipManager; // Adjust constructor to match your actual dependency setup public MembershipSynchronizer(MembershipManager membershipManager) { _membershipManager = membershipManager; // Initialize _membershipPopulatorFactory as needed for your code } /// <summary> /// Performs a single run of the synchronization service. /// </summary> public void Synchronize(CancellationToken cancellationToken) { try { var stopwatch = Stopwatch.StartNew(); var populator = _membershipPopulatorFactory.CreatePopulator(); // Pass cancellation token to the long-running operation populator.PopulateMembership(cancellationToken); TimeSpan elapsed = stopwatch.Elapsed; LoggingUtility.LogTrace(LoggingCategory.Membership, $"Synchronization completed in {elapsed.TotalSeconds:F2} seconds."); } catch (OperationCanceledException) { // Re-throw to let the parent class handle cancellation throw; } catch (Exception e) { LoggingUtility.LogTrace(LoggingCategory.Membership, e); } } }
3. Update Class3: Populator
Modify the PopulateMembership method to accept a CancellationToken, and insert cancellation checks at logical points in your long-running code (e.g., inside loops, before expensive operations).
public sealed class Populator : IDisposable, IMembershipPopulator { public void PopulateMembership(CancellationToken cancellationToken) { // Example: Loop through a large dataset (replace with your actual logic) foreach (var membershipItem in FetchMembershipData()) { // Check for cancellation at the start of each iteration cancellationToken.ThrowIfCancellationRequested(); // Process individual membership item ProcessMembershipRecord(membershipItem); // Optional: Check again if each item processing is long-running cancellationToken.ThrowIfCancellationRequested(); } // Check before another sequential expensive operation cancellationToken.ThrowIfCancellationRequested(); FinalizeMembershipUpdates(); } // Helper methods representing your existing long-running logic private IEnumerable<object> FetchMembershipData() { // Your existing data fetching logic yield break; } private void ProcessMembershipRecord(object item) { // Your existing record processing logic } private void FinalizeMembershipUpdates() { // Your existing finalization logic } // Existing IDisposable implementation public void Dispose() { // Clean up any resources used by the populator } }
Key Implementation Notes
- Cancellation Check Frequency: Add
cancellationToken.ThrowIfCancellationRequested()at frequent enough intervals to ensure the operation responds promptly to stop requests. For blocking calls (like database queries or API calls), use overloads that accept aCancellationTokento cancel those operations directly. - Thread Safety: The
lock (_sync)ensures no concurrent runs ofRunOnceand safe triggering of cancellation during disposal. - Exception Handling:
OperationCanceledExceptionis thrown when cancellation is requested. We catch it inRunOnceto handle it gracefully without letting it become an unhandled exception. - Resource Cleanup: Always dispose of
CancellationTokenSourcewhen it's no longer needed to prevent resource leaks.
内容的提问来源于stack exchange,提问作者user14285851

