2014-02-06 64 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