取消线程并重新启动它

时间:2014-02-06 18:42:07

标签: c# multithreading

当用户调整窗口大小时,应该更新一些长文本,但如果线程已经运行,则应该停止并使用新的宽度参数重新开始。

int myWidth;
private CancellationTokenSource tokenSource2 = new CancellationTokenSource();
private CancellationToken ct = new CancellationToken();

void container_Loaded(object sender, RoutedEventArgs e)
{
  ct = tokenSource2.Token;
  MyFunction();
}

        void container_SizeChanged(object sender, SizeChangedEventArgs e)
        {
          if (tokenSource2.Token.IsCancellationRequested)
            MyFunction();
          else
            tokenSource2.Cancel();
        }

        void MyFunction()            
        {
           myWidth = GetWidth();
           Task.Factory.StartNew(() =>
           {  
              string s;    
              for (int i=0;i<1000,i++){
                  s=s+Functionx(myWidth);
                  ct.ThrowIfCancellationRequested();
              }
              this.Dispatcher.BeginInvoke(new Action(() => { 
                   ShowText(s); 
              }));
           },tokenSource2.Token)
           .ContinueWith(t => {
              if (t.IsCanceled)
              {
                tokenSource2 = new CancellationTokenSource(); //reset token
                MyFunction(); //restart
              };
           });
        }

现在发生的事情是当我调整窗口大小时,我看到文本在接下来的几秒内迭代地更新,好像旧线程没有被取消一样。我做错了什么?

2 个答案:

答案 0 :(得分:1)

在这种情况下,我认为使用全局变量不是一个好主意。以下是我使用AsyncOp从相关问题Correctly cancel async operation and fire it again的答案中执行此操作的方法:

using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace Wpf_21611292
{
    // Model
    public class ViewModel : INotifyPropertyChanged
    {
        string _data;

        public string Data
        {
            get
            {
                return _data;
            }
            set
            {
                if (_data != value)
                {
                    _data = value;
                    if (this.PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Data"));
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

    // MainWindow
    public partial class MainWindow : Window
    {
        ViewModel _model = new ViewModel { Data = "Hello!" };

        AsyncOp _asyncOp = new AsyncOp();

        CancellationTokenSource _myFunctionCts = new CancellationTokenSource();

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = _model;


            this.Loaded += MainWindow_Loaded;
            this.SizeChanged += MainWindow_SizeChanged;
        }

        void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            _asyncOp.RunAsync(MyFunctionAsync, _myFunctionCts.Token);
        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            _asyncOp.RunAsync(MyFunctionAsync, _myFunctionCts.Token);
        }

        async Task MyFunctionAsync(CancellationToken token)
        {
            int width = (int)this.Width;
            var text = await Task.Run(() =>
            {
                int i;
                for (i = 0; i < 10000000; i++)
                {
                    if (token.IsCancellationRequested)
                        break;
                }
                return i;
            }, token);

            // update ViewModel
            _model.Data = "Width: " + width.ToString() + "/" + text;
        }
    }

    // AsyncOp
    class AsyncOp
    {
        Task _pendingTask = null;
        CancellationTokenSource _pendingCts = null;

        public Task PendingTask { get { return _pendingTask; } }

        public void Cancel()
        {
            if (_pendingTask != null && !_pendingTask.IsCompleted)
                _pendingCts.Cancel();
        }

        public Task RunAsync(Func<CancellationToken, Task> routine, CancellationToken token)
        {
            var oldTask = _pendingTask;
            var oldCts = _pendingCts;

            var thisCts = CancellationTokenSource.CreateLinkedTokenSource(token);

            Func<Task> startAsync = async () =>
            {
                // await the old task
                if (oldTask != null && !oldTask.IsCompleted)
                {
                    oldCts.Cancel();
                    try
                    {
                        await oldTask;
                    }
                    catch (Exception ex)
                    {
                        while (ex is AggregateException)
                            ex = ex.InnerException;
                        if (!(ex is OperationCanceledException))
                            throw;
                    }
                }
                // run and await this task
                await routine(thisCts.Token);
            };

            _pendingCts = thisCts;

            _pendingTask = Task.Factory.StartNew(
                startAsync,
                _pendingCts.Token,
                TaskCreationOptions.None,
                TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();

            return _pendingTask;
        }
    }
}

XAML:

<Window x:Class="Wpf_21611292.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <TextBox Width="200" Height="30" Text="{Binding Path=Data}"/>
        <TextBox Width="200" Height="30" Text="{Binding Path=Data}"/>
        <TextBox Width="200" Height="30" Text="{Binding Path=Data}"/>
        <TextBox Width="200" Height="30" Text="{Binding Path=Data}"/>
        <TextBox Width="200" Height="30" Text="{Binding Path=Data}"/>
        <TextBox Width="200" Height="30" Text="{Binding Path=Data}"/>
        <TextBox Width="200" Height="30" Text="{Binding Path=Data}"/>
        <TextBox Width="200" Height="30" Text="{Binding Path=Data}"/>
        <TextBox Width="200" Height="30" Text="{Binding Path=Data}"/>
    </StackPanel>
</Window>

它使用async/await,因此如果您定位.NET 4.0,则需要Microsoft.Bcl.Async和VS2012 +。或者,您可以将async/await转换为ContinueWith,这有点单调乏味,但总是可行的(这或多或少是C#5.0编译器在场景后的作用)。

答案 1 :(得分:1)

您应该使用Microsoft的Reactive Framework(又名Rx)-NuGet System.Reactive.Windows.Threading(对于WPF)并添加using System.Reactive.Linq;-然后,您可以这样做:

    public MainWindow()
    {
        InitializeComponent();

        _subscription =
            Observable
                .FromEventPattern<SizeChangedEventHandler, SizeChangedEventArgs>(
                    h => container.SizeChanged += h,
                    h => container.SizeChanged -= h)
                .Select(e => GetWidth())
                .Select(w => Observable.Start(
                        () => String.Concat(Enumerable.Range(0, 1000).Select(n => Functionx(w)))))
                .Switch()
                .ObserveOnDispatcher()
                .Subscribe(t => ShowText(t));
    }

    private IDisposable _subscription = null;

这就是所有需要的代码。

这将响应SizeChanged事件,调用GetWidth,然后将Functionx推送到另一个线程。它使用Switch()始终切换到最新的SizeChanged,然后忽略任何运行中的代码。它将结果推送到调度程序,然后调用ShowText

如果您需要关闭表单或停止运行订阅,只需致电_subscription.Dispose()

简单。