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

如何在WinForms项目中优雅地跨类传递CancellationToken以取消成员资格同步长耗时操作

Optimal Solution for Graceful Cancellation of Long-Running WinForms Operation

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 CancellationTokenSource in MembershipSynchronizationService to manage cancellation state
  • Pass the associated CancellationToken through the call chain (Class1 → Class2 → Class3)
  • Modify the long-running method in Class3 to periodically check for cancellation requests
  • Trigger cancellation in the Dispose method of MembershipSynchronizationService
  • 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 a CancellationToken to cancel those operations directly.
  • Thread Safety: The lock (_sync) ensures no concurrent runs of RunOnce and safe triggering of cancellation during disposal.
  • Exception Handling: OperationCanceledException is thrown when cancellation is requested. We catch it in RunOnce to handle it gracefully without letting it become an unhandled exception.
  • Resource Cleanup: Always dispose of CancellationTokenSource when it's no longer needed to prevent resource leaks.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 06:33:12