如何在应用程序等待所有任务完成时使“取消”按钮可单击

本文关键字:取消 按钮 单击 完成时 任务 应用程序 等待 | 更新日期: 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,因此"取消"按钮不可单击。有没有更好的方法?

如何在应用程序等待所有任务完成时使“取消”按钮可单击

有几件事你做错了:

  1. 使用async-await,您需要使用 Task.Run 而不是 Task.TaskFactory.StartNew .
  2. 从 UI 线程调用 Task.ResultTask.WaitTask.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做的事情联系起来。你不应该把AsParallelTask混为一谈。您的处理要么是 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的基础知识(而不是基础知识)。

注意:某些代码可能无法编译,需要稍作调整。