如何在应用程序等待所有任务完成时使“取消”按钮可单击
本文关键字:取消 按钮 单击 完成时 任务 应用程序 等待 | 更新日期: 2023-09-27 18:35:32
这是我的主窗口。
<Window x:Class="WpfApplication2.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">
<Grid Loaded="EmployeesGridLoaded">
<Grid.RowDefinitions>
<RowDefinition Height="6*" />
<RowDefinition />
</Grid.RowDefinitions>
<DataGrid x:Name="gEmployees" HorizontalAlignment="Left" Margin="10,10,0,0"
VerticalAlignment="Top" AlternatingRowBackground="LightBlue" AlternationCount="2" AutoGenerateColumns="False" Grid.Row="0">
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Select" Binding="{Binding Select}" Width="1*" />
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}" Width="3*" />
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" Width="3*" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="5" Grid.Row="1" >
<Button Content="Process" Margin="5" Click="Process_Click" />
<Button Content="Cancel" Margin="5" Click="Cancel_Click" />
</StackPanel>
</Grid>
</Window>
这是代码。
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private List<Employee> Employees = null;
public MainWindow()
{
InitializeComponent();
}
private void EmployeesGridLoaded(object sender, RoutedEventArgs e)
{
Employees = new List<Employee>()
{
new Employee() { Select = false, LastName = "Silly", FirstName = "Dude" },
new Employee() { Select = false, LastName = "Mean", FirstName = "Person" },
new Employee() { Select = false, LastName = "New", FirstName = "Friend" },
new Employee() { Select = false, LastName = "My", FirstName = "Buddy" },
};
gEmployees.ItemsSource = Employees;
}
private void Process_Click(object sender, RoutedEventArgs e)
{
Task task = Task.Factory.StartNew(() =>
{
string[] tResults = Employees
.Where(x => x.Select == true)
.AsParallel()
.Select(x => this.DoSomethingWithThisEmployeee(x.LastName, x.FirstName).Result)
.ToArray();
});
Task.WaitAll(task);
System.Windows.MessageBox.Show("Done");
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
}
private Task<string> DoSomethingWithThisEmployeee(string lastName, string firstName)
{
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
Random rand = new Random();
System.Threading.Thread.Sleep(rand.Next(30000, 60000));
tcs.SetResult(lastName + " - " + firstName);
return tcs.Task;
}
}
}
在代码中,对于每个选定的员工,我必须执行一些长时间运行的操作。为所有选定的员工执行长时间运行的操作后,应用将执行其他操作。但是,当这些长时间运行的操作正在运行时,我希望"取消"按钮可用,以便用户可以取消操作。问题是 Task.WaitAll 会阻止 UI,因此"取消"按钮不可单击。有没有更好的方法?
有几件事你做错了:
- 使用
async-await
,您需要使用Task.Run
而不是Task.TaskFactory.StartNew
. - 从 UI 线程调用
Task.Result
、Task.Wait
或Task.WaitAll
会使 UI 死锁。
使用 async-await
如果您将逻辑完全拆分为 UI 和非 UI,如果更容易,则会容易得多:
private async void Process_Click(object sender, RoutedEventArgs e)
{
var results = await ProcessAsync();
System.Windows.MessageBox.Show("Done");
}
异步方法提供异步执行逻辑,大多数情况下,甚至不需要Task.Run
。
如果我理解你的例子,你正试图将DoSomethingWithThisEmployeee
做的事情联系起来。你不应该把AsParallel
和Task
混为一谈。您的处理要么是 CPU 密集型的(为此你应该使用 AaParallel
,如果处理每个项目不依赖于处理另一个项目)或阻塞(为此你应该使用 Task
s)。
假设您的处理代码阻塞,您可以像这样编写它:
private async Task<IEnumerable<string>> ProcessAsync()
{
var tasks =
from e in this.Employees
where e.Select
select this.DoSomethingWithThisEmployeee(e.LastName, e.FirstName);
return await Task.WhenAll(runningTasks);
}
为了支持取消,您需要使用CancellationToken
并将其传递给接受CancellationToken
的所有代码,并使用它来取消执行:
private CancellationTokenSource tcs;
private async void Process_Click(object sender, RoutedEventArgs e)
{
try
{
this.tcs = new CancellationTokenSource();
var results = await ProcessAsync(tcs.Token);
System.Windows.MessageBox.Show("Done");
}
catch OperationCanceledException ex
{
System.Windows.MessageBox.Show("Canceled");
}
catch Exception ex
{
System.Windows.MessageBox.Show("Error");
}
}
private async void Cancel_Click(object sender, RoutedEventArgs e)
{
this.tcs.Cancel();
}
private async Task<IEnumerable<string>> ProcessAsync(CancellationToken ct)
{
var tasks =
from e in this.Employees
where e.Select
select this.DoSomethingWithThisEmployeee(e.LastName, e.FirstName, ct);
return await Task.WhenAll(runningTasks);
}
您的DoSomethingWithThisEmployeee
方法也存在问题。它总是在"处理"数据后返回已完成的任务。
我相信你想要的是更多的东西:
private Task<string> DoSomethingWithThisEmployeee(string lastName, string firstName, CancellationToken ct)
{
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
Random rand = new Random();
System.Threading.Timer t;
t = new System.Threading.Timer(
() =>
{
try
{
t.Dispose();
tcs.ThrowIfCancellationRequested();
tcs.TrySetResult(lastName + " - " + firstName);
}
catch OperationCanceledException ex
{
}
catch Exception ex
{
tcs.TrySetException(ex);
}
},
rand.Next(30000, 60000),
Timeout.Infinite);
return tcs.Task;
}
您可能还需要使用 Task.ConfigureAwait(false)
,但我会把它留给你。
阅读有关我的async-await
策展的文章,以了解有关async-await
的基础知识(而不是基础知识)。
注意:某些代码可能无法编译,需要稍作调整。