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

C# 如何在应用程序等待所有任务完成时单击“取消”按钮,c#,.net,wpf,c#-4.0,C#,.net,Wpf,C# 4.0,这是我的主窗口 <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">

这是我的主窗口

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

}
命名空间WpfApplication2
{
/// 
///MainWindow.xaml的交互逻辑
/// 
公共部分类主窗口:窗口
{
私有列表员工=null;
公共主窗口()
{
初始化组件();
}
private void EmployeesGridLoaded(对象发送方,路由目标)
{
雇员=新名单()
{
新员工(){Select=false,LastName=“傻”,FirstName=“Dude”},
新员工(){Select=false,LastName=“Mean”,FirstName=“Person”},
新员工(){Select=false,LastName=“new”,FirstName=“Friend”},
新员工(){Select=false,LastName=“My”,FirstName=“Buddy”},
};
gEmployees.ItemsSource=员工;
}
私有作废处理\u单击(对象发送方,路由目标)
{
Task Task=Task.Factory.StartNew(()=>
{
字符串[]tResults=Employees
.Where(x=>x.Select==true)
.天冬酰胺()
.Select(x=>this.DoSomethingWithThisEmployeee(x.LastName,x.FirstName).Result)
.ToArray();
});
Task.WaitAll(任务);
System.Windows.MessageBox.Show(“完成”);
}
私有无效取消\单击(对象发送者,路由目标)
{
}
私有任务doSomethingWithThisEmployee(字符串lastName,字符串firstName)
{
TaskCompletionSource tcs=新的TaskCompletionSource();
Random rand=新的Random();
系统线程线程睡眠(rand.Next(3000060000));
tcs.SetResult(lastName+“-”+firstName);
返回tcs.Task;
}
}
}


在代码中,对于每个选定的员工,我必须执行一些长时间运行的操作。一旦为所有选定的员工执行了长时间运行的操作,应用程序将执行其他操作。但是,当这些长时间运行的操作正在运行时,我希望“取消”按钮可用,以便用户可以取消操作。问题是Task.WaitAll会阻塞UI,因此无法单击“取消”按钮。有更好的方法吗?

有几件事你做错了:

  • 使用
    异步等待
  • 从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");
    }
    
    异步方法提供异步执行逻辑,在大多数情况下,甚至不需要
    任务。运行

    如果我理解了你的例子,那么你是在尝试将
    所做的事情与该员工的
    所做的事情进行对比。你不应该把
    AsParallel
    Task
    s混在一起。您的处理是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);
    } 
    
    您的
    DoSomething使用此Employeee
    方法也有问题。它总是在“处理”数据后返回已完成的任务

    我相信你想要的是更多的东西:

    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;
    }
    
    private Task DoSomethingWithThisEmployeee(字符串lastName、字符串firstName、CancellationToken ct)
    {
    TaskCompletionSource tcs=新的TaskCompletionSource();
    Random rand=新的Random();
    System.Threading.Timer t;
    t=新的System.Threading.Timer(
    () =>
    {
    尝试
    {
    t、 处置();
    tcs.ThrowIfCancellationRequested();
    tcs.TrySetResult(lastName+“-”+firstName);
    }
    捕获操作取消异常
    {
    }
    捕获异常
    {
    tSystexception(ex);
    }
    },
    下一个兰特(3000060000),
    超时(无限);
    返回tcs.Task;
    }
    
    您可能还需要使用
    Task.ConfigureAwait(false)
    ,但我将留给您

    阅读上的文章,了解有关
    async wait
    的基本知识(并非如此)


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

    首先,您不能对单个任务执行
    WaitAll
    。无论如何,您应该使用continuation。或者移动到.NET4.5并使用异步等待。@Hamlet:我不能移动到.NET4.5。所以继续不会导致阻塞?任务完成后将触发继续。使用cancellationtokensource将cancellationtoken传递给每个任务。您需要在每个任务的代码中处理取消,但这允许您启动任务并将控制权返回到UI。然后,cancel按钮将取消cancellationtokensource。这些都是从.Net 4开始的感谢您的详细回复,但是,我只能使用.Net 4.0.OK!我错过了
    C#-4.0
    标签。但是您确实用
    asyncwait
    标记了这个问题,它是C#5.0。你不能用吗?那是我的错。我正在工作的组织有一些奇怪的过程,在引入对较新库的依赖时必须经历这些过程:(如果可能的话,我宁愿不经历痛苦的过程。您仍然可以使用
    CancellationToken
    s和线程,而不是任务。
    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;
    }