Cancel thread and restart it

前端 未结 2 1272
萌比男神i
萌比男神i 2020-12-20 06:33

When user resizes window some long text should be updated, but if the thread is already running it should be stopped and started over with new width parameter.



        
2条回答
  •  盖世英雄少女心
    2020-12-20 07:00

    I don't think using global variables is a good idea in this case. Here's how I would do it by adding cancellation logic to my AsyncOp class from a related question. This code also implements the IProgress pattern and throttles the ViewModel updates.

    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    
    namespace Wpf_21611292
    {
        /// 
        /// Cancel and restarts an asynchronous operation
        /// 
        public class AsyncOp
        {
            readonly object _lock = new object();
            Task _pendingTask = null;
            CancellationTokenSource _pendingCts = null;
    
            public Task CurrentTask
            {
                get { lock (_lock) return _pendingTask; }
            }
    
            public bool IsPending
            {
                get { lock (_lock) return _pendingTask != null && !_pendingTask.IsCompleted; }
            }
    
            public bool IsCancellationRequested
            {
                get { lock (_lock) return _pendingCts != null && _pendingCts.IsCancellationRequested; }
            }
    
            public void Cancel()
            {
                lock (_lock)
                {
                    if (_pendingTask != null && !_pendingTask.IsCompleted && !_pendingCts.IsCancellationRequested)
                        _pendingCts.Cancel();
                }
            }
    
            public Task Run(
                Func> routine,
                CancellationToken token = default,
                bool startAsync = false,
                bool continueAsync = false,
                TaskScheduler taskScheduler = null)
            {
                Task previousTask = null;
                CancellationTokenSource previousCts = null;
    
                Task thisTask = null;
                CancellationTokenSource thisCts = null;
    
                async Task routineWrapper()
                {
                    // await the old task
                    if (previousTask != null)
                    {
                        if (!previousTask.IsCompleted && !previousCts.IsCancellationRequested)
                        {
                            previousCts.Cancel();
                        }
                        try
                        {
                            await previousTask;
                        }
                        catch (Exception ex)
                        {
                            if (!(previousTask.IsCanceled || ex is OperationCanceledException))
                                throw;
                        }
                    }
    
                    // run and await this task
                    return await routine(thisCts.Token);
                };
    
                Task> outerTask;
    
                lock (_lock)
                {
                    previousTask = _pendingTask;
                    previousCts = _pendingCts;
    
                    thisCts = CancellationTokenSource.CreateLinkedTokenSource(token);
    
                    outerTask = new Task>(
                        routineWrapper,
                        thisCts.Token,
                        continueAsync ?
                            TaskCreationOptions.RunContinuationsAsynchronously :
                            TaskCreationOptions.None);
    
                    thisTask = outerTask.Unwrap();
    
                    _pendingTask = thisTask;
                    _pendingCts = thisCts;
                }
    
                var scheduler = taskScheduler;
                if (scheduler == null)
                {
                    scheduler = SynchronizationContext.Current != null ?
                        TaskScheduler.FromCurrentSynchronizationContext() :
                        TaskScheduler.Default;
                }
    
                if (startAsync)
                    outerTask.Start(scheduler);
                else
                    outerTask.RunSynchronously(scheduler);
    
                return thisTask;
            }
        }
    
        /// 
        /// ViewModel
        /// 
        public class ViewModel : INotifyPropertyChanged
        {
            string _width;
    
            string _text;
    
            public string Width
            {
                get
                {
                    return _width;
                }
                set
                {
                    if (_width != value)
                    {
                        _width = value;
                        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Width)));
                    }
                }
            }
    
            public string Text
            {
                get
                {
                    return _text;
                }
                set
                {
                    if (_text != value)
                    {
                        _text = value;
                        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
                    }
                }
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
        }
    
        /// 
        /// MainWindow
        /// 
        public partial class MainWindow : Window
        {
            ViewModel _model = new ViewModel { Text = "Starting..." };
    
            AsyncOp _asyncOp = new AsyncOp();
    
            CancellationTokenSource _workCts = new CancellationTokenSource();
    
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = _model;
    
                this.Loaded += MainWindow_Loaded;
                this.SizeChanged += MainWindow_SizeChanged;
            }
    
            void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
            {
                _asyncOp.Run(WorkAsync, _workCts.Token);
            }
    
            void MainWindow_Loaded(object sender, RoutedEventArgs e)
            {
                _asyncOp.Run(WorkAsync, _workCts.Token);
            }
    
            async Task WorkAsync(CancellationToken token)
            {
                const int limit = 200000000;
                var throttle = TimeSpan.FromMilliseconds(200);
    
                // update ViewModel's Width
                _model.Width = $"Width: {this.Width:#.##}";
    
                // update ViewModel's Text using IProgress pattern 
                // and throttling updates
                IProgress progress = new Progress(i =>
                {
                    _model.Text = $"{(double)i / (limit - 1)* 100:0.}%";
                });
    
                var stopwatch = new Stopwatch();
                stopwatch.Start();
    
                // do some CPU-intensive work
                await Task.Run(() =>
                {
                    int i;
                    for (i = 0; i < limit; i++)
                    {
                        if (stopwatch.Elapsed > throttle)
                        {
                            progress.Report(i);
                            stopwatch.Restart();
                        }
                        if (token.IsCancellationRequested)
                            break;
                    }
                    progress.Report(i);
                }, token);
    
                return DBNull.Value;
            }
        }
    }
    

    XAML:

    
        
            
            
        
    
    

    It uses async/await, so if you target .NET 4.0, you'd need Microsoft.Bcl.Async and VS2012+. Alternatively, you can convert async/await to ContinueWith, which is a bit tedious, but always possible (that's more or less what the C# 5.0 compiler does behind the scene).

提交回复
热议问题