当用户调整窗口大小时,应该更新一些长文本,但如果线程已经运行,则应该停止并使用新的宽度参数重新开始。
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
};
});
}
现在发生的事情是当我调整窗口大小时,我看到文本在接下来的几秒内迭代地更新,好像旧线程没有被取消一样。我做错了什么?
答案 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()
。
简单。