2014-02-06 115 views
1

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

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 
       }; 
      }); 
     } 

现在发生的事情是,当我调整窗口,我看到的文字反复地更新下一个几秒钟,就好像老线程没有取消。我究竟做错了什么?

+3

你从来没有真正取消线程。每个调整大小增量开始另一个任务。 –

+0

你说得对。在我看来,我能做的唯一事情就是让这些众多对象中的每一个都有一个全局任务,我可以在运行时检查调整大小,然后task = null,task = new Task.Factory ...您对此有何看法@ HansPassant – Daniel

+0

'if(tokenSource2.Token.IsCancellationRequested)tokenSource2.Cancel();' - 只有当'IsCancellationRequested' **已经是'true'时,才调用'Cancel()',这是没有意义的。你是不是指'if(!tokenSource2.Token.IsCancellationRequested)tokenSource2.Cancel();'? – Noseratio

回答

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编译器在场景后面做的)。

+0

你能解释一下它是如何工作的吗?我想如果你在开始任务时发送令牌,它根本不会启动。我不明白它取消之前的任务并开始新的任务。此外,它应该迭代到1000,只是将宽度传递给另一个函数。 – Daniel

+0

@MiloS,请注意'MyFunctionAsync'中的'for'循环,它就是迭代的地方,您可以改为调用'MyFunctionX'。它的工作原理是取消先前的任务实例,并异步等待取消完成。看看它是如何发生的最好方法是在调试器中进行指导。 – Noseratio

+0

安装Microsoft.Bcl.Async它表示它不需要/支持.net 4.5,也不支持AsyncOp。顺便说一下,您的解决方案是为每个控件创建两个任务吗? – Daniel