简体   繁体   English

在进行长时间运行的操作之前立即取消操作吗?

[英]Cancel operation right away before going through long running operations?

I am using AsParallel combined with WithDegreeOfParallelism and WithCancellation in following way 我正在以以下方式将AsParallel与WithDegreeOfParallelism和WithCancellation结合使用

AsParallel().WithCancellation(cs.Token).WithDegreeOfParallelism(2)

This is my understanding about this. 这是我对此的理解。 Only two of the incoming sequence will be processed at a time. 一次仅处理两个传入序列。 Once one of the request completes, then more items will be processed. 请求之一完成后,将处理更多项目。 However, if cancellation request is initiated, than those items from incoming queue that are not been picked up yet will be processed at all. 但是,如果发起取消请求,则来自入局队列中尚未拾取的那些项目将被处理。 Based on this understanding, I have created following code. 基于这种理解,我创建了以下代码。

class Employee
    {
        public int ID { get; set;}
        public string FirstName { get; set;}
        public string LastName { get; set;}
    }

    class Program
    {

        private static List<Employee> _Employees;
        static CancellationTokenSource cs = new CancellationTokenSource();
        static Random rand = new Random();

        static void Main(string[] args)
        {
            _Employees = new List<Employee>() 
            {
                new Employee() { ID = 1, FirstName = "John", LastName = "Doe" },
                new Employee() { ID = 2, FirstName = "Peter", LastName = "Saul" },
                new Employee() { ID = 3, FirstName = "Mike", LastName = "Sue" },
                new Employee() { ID = 4, FirstName = "Catherina", LastName = "Desoza" },
                new Employee() { ID = 5, FirstName = "Paul", LastName = "Smith" },
                new Employee() { ID = 6, FirstName = "Paul2", LastName = "Smith" },
                new Employee() { ID = 7, FirstName = "Paul3", LastName = "Smith" },
                new Employee() { ID = 8, FirstName = "Paul4", LastName = "Smith" },
                new Employee() { ID = 9, FirstName = "Paul5", LastName = "Smith" },
                new Employee() { ID = 10, FirstName = "Paul6", LastName = "Smith" },
                new Employee() { ID = 5, FirstName = "Paul7", LastName = "Smith" }
            };

            try
            {
                var tasks = _Employees.AsParallel().WithCancellation(cs.Token).WithDegreeOfParallelism(2).Select(x => ProcessThisEmployee(x, cs.Token)).ToArray();
                Console.WriteLine("Now waiting");
                Thread.Sleep(1000);
                cs.Cancel();
                Task.WaitAll(tasks);
            }
            catch (AggregateException ae)
            {
                // error handling code
                Console.WriteLine("something bad happened");
            }
            catch (Exception ex)
            {
                // error handling code
                Console.WriteLine("something even worst happened");
            }
            // other stuff
            Console.WriteLine("All Done");
        }

        private static async Task ProcessThisEmployee(Employee x, CancellationToken token)
        {
            if (token.IsCancellationRequested)
            {
                Console.WriteLine(string.Format("ThreadID = {0} -> Employee {1} -> Cancelled", System.Threading.Thread.CurrentThread.ManagedThreadId));
                return;
            }
            int Sleep = rand.Next(800, 2000);
            Console.WriteLine(string.Format("ThreadID = {0} -> Employee {1} -> Sleeping for {2}", System.Threading.Thread.CurrentThread.ManagedThreadId, x.ID, Sleep));
            await TaskEx.Run(() => System.Threading.Thread.Sleep(Sleep));

            Console.WriteLine(string.Format("ThreadID = {0} -> Employee {1} finished", System.Threading.Thread.CurrentThread.ManagedThreadId, x.ID));
        }

    }

Here is output when I ran it. 这是我运行它时的输出。

ThreadID = 3 -> Employee 1 -> Sleeping for 1058 线程ID = 3->员工1->睡1058
ThreadID = 1 -> Employee 7 -> Sleeping for 1187 线程ID = 1->雇员7->睡觉1187
ThreadID = 1 -> Employee 8 -> Sleeping for 1296 线程ID = 1->员工8->睡觉1296
ThreadID = 1 -> Employee 9 -> Sleeping for 1614 线程ID = 1->员工9->睡1614
ThreadID = 1 -> Employee 10 -> Sleeping for 1607 线程ID = 1->员工10->睡觉1607
ThreadID = 1 -> Employee 5 -> Sleeping for 1928 线程ID = 1->雇员5->睡觉1928
ThreadID = 3 -> Employee 2 -> Sleeping for 1487 线程ID = 3->员工2->睡觉1487
ThreadID = 3 -> Employee 3 -> Sleeping for 1535 线程ID = 3->员工3->睡觉1535
ThreadID = 3 -> Employee 4 -> Sleeping for 1265 线程ID = 3->员工4->睡觉1265
ThreadID = 3 -> Employee 5 -> Sleeping for 1248 线程ID = 3->员工5->睡1248
ThreadID = 3 -> Employee 6 -> Sleeping for 807 线程ID = 3->雇员6->睡觉807
Now waiting 现在正在等待
ThreadID = 3 -> Employee 6 finished 线程ID = 3->员工6已完成
ThreadID = 4 -> Employee 1 finished 线程ID = 4->员工1完成
ThreadID = 5 -> Employee 7 finished 线程ID = 5->员工7完成
ThreadID = 6 -> Employee 8 finished 线程ID = 6->员工8完成
ThreadID = 3 -> Employee 5 finished 线程ID = 3->员工5完成
ThreadID = 4 -> Employee 9 finished 线程ID = 4->员工9完成
ThreadID = 5 -> Employee 10 finished 线程ID = 5->员工10完成
ThreadID = 6 -> Employee 5 finished 线程ID = 6->员工5完成
ThreadID = 3 -> Employee 4 finished 线程ID = 3->员工4完成
ThreadID = 7 -> Employee 2 finished 线程ID = 7->员工2完成
ThreadID = 8 -> Employee 3 finished 线程ID = 8->员工3完成
All Done 全部做完

Here are my issues (according to my understanding of things). 这是我的问题(根据我对事物的理解)。

  1. I was expecting that for some employees ProcessThisEmployee will not be called at all because it will be cancelled but its called for all employees 我期望对于某些员工,ProcessThisEmployee根本不会被调用,因为它将被取消,但它会为所有员工调用
  2. Even if ProcessThisEmployee method is called, it will go through following code path that is also not happening 即使调用ProcessThisEmployee方法,它也会通过以下代码路径进行操作

     if ( token.IsCancellationRequested ) { Console.WriteLine(string.Format("ThreadID = {0} -> Employee {1} -> Cancelled",System.Threading.Thread.CurrentThread.ManagedThreadId)); return; } 

So then I changed ProcessThisEmployee, basically moved the token.IsCancellationRequested message after Sleep as follows. 因此,我更改了ProcessThisEmployee,基本上将Sleep。之后的token.IsCancellationRequested消息移动如下。

private static async Task ProcessThisEmployee(Employee x, CancellationToken token)
{

    int Sleep = rand.Next(800, 2000);
    Console.WriteLine(string.Format("ThreadID = {0} -> Employee {1} -> Sleeping for {2}", System.Threading.Thread.CurrentThread.ManagedThreadId, x.ID, Sleep));
    await TaskEx.Run(() => System.Threading.Thread.Sleep(Sleep));
    if (token.IsCancellationRequested)
    {
        Console.WriteLine(string.Format("ThreadID = {0} -> Employee {1} -> Cancelled", System.Threading.Thread.CurrentThread.ManagedThreadId));
        return;
    }
    Console.WriteLine(string.Format("ThreadID = {0} -> Employee {1} finished", System.Threading.Thread.CurrentThread.ManagedThreadId, x.ID));
}

Now I get following output. 现在我得到以下输出。

ThreadID = 3 -> Employee 1 -> Sleeping for 1330  
ThreadID = 1 -> Employee 7 -> Sleeping for 1868  
ThreadID = 3 -> Employee 2 -> Sleeping for 903  
ThreadID = 3 -> Employee 3 -> Sleeping for 1241  
ThreadID = 3 -> Employee 4 -> Sleeping for 1367  
ThreadID = 3 -> Employee 5 -> Sleeping for 1007  
ThreadID = 3 -> Employee 6 -> Sleeping for 923  
ThreadID = 1 -> Employee 8 -> Sleeping for 1032  
ThreadID = 1 -> Employee 9 -> Sleeping for 1948  
ThreadID = 1 -> Employee 10 -> Sleeping for 1456  
ThreadID = 1 -> Employee 5 -> Sleeping for 1737  
Now waiting  
ThreadID = 5 -> Employee 2 finished  
ThreadID = 3 -> Employee 6 finished  
something bad happened  
All Done  

My question is what am I mis-understanding about this workflow. 我的问题是我对这个工作流程有什么误解。 I basically would like to cancel the operation as soon as possible without going through long running operation (Sleep is just an example in this case but it could be something really expensive) 我基本上想尽快取消该操作而不进行长时间运行的操作(在这种情况下,Sleep仅是一个示例,但可能会非常昂贵)

There are a few problems with that code: 该代码存在一些问题:

1.) ToArray() materializes the sequence, ie it will only return after all inputs from the source sequence have been gone through the Select(...) . 1.) ToArray()实现序列,即只有在源序列的所有输入都经过Select(...)之后,它才会返回。

Since you call cs.Cancel() after that it won't trigger the token.IsCancellationRequested immediately at the start of the ProcessThisEmployee 由于您在此之后调用cs.Cancel() ,因此它不会触发token.IsCancellationRequestedProcessThisEmployee开始时立即

2.) WithDegreeOfParallelism(2).Select(x => ProcessThisEmployee(x, cs.Token)) looks good but in fact isn't really doing what you want it to do since ProcessThisEmployee is an async method that returns as soon as the first return or the first await is reached. 2.) WithDegreeOfParallelism(2).Select(x => ProcessThisEmployee(x, cs.Token))看起来不错,但实际上并没有真正按照您的意愿去做,因为ProcessThisEmployee是一个异步方法,它会在第一次返回或到达第一次等待。

What you probably wanted to do is to execute the long running ProcessThisEmployee method with only 2 degrees of parallelism. 您可能想要做的是仅使用2个并行度来执行长时间运行的ProcessThisEmployee方法。 What you actually do is create a bunch of Tasks with only 2 degrees of parallelism. 您实际要做的是创建只有2个并行度的Tasks The tasks itself all run concurrently after that. 之后,任务本身全部同时运行。

I don't know how to fix this for your particular case, as I don't know the context. 我不知道如何解决您的具体情况,因为我不知道具体情况。 But maybe this already helps you a bit. 但这也许已经对您有所帮助。


Update to reply to your comment: I am doing ToArray and ProcessThisEmployee is an async method because this code will become part of library and could be used from WPF application. 更新以回复您的评论: 我正在做ToArray,而ProcessThisEmployee是异步方法,因为此代码将成为库的一部分,并且可以从WPF应用程序中使用。 End user may wants to provide updates on the UI, so I don't want to block until operation completes (john smith) 最终用户可能希望在UI上提供更新,所以我不想在操作完成之前阻止更新 (John Smith)

Don't write async wrappers for things that aren't asynchronous by nature, ie mostly file, network or database access. 不要为本质上不是异步的东西编写异步包装器,即主要是文件,网络或数据库访问。 If the developer using a library wants to call something in an async context he can still do an await Task.Run(...) . 如果使用库的开发人员想要在异步上下文中调用某些内容,他仍然可以执行await Task.Run(...) For more information about this you can take a look at this article about whether you should expose asynchronous wrappers for synchronous methods . 有关此的更多信息,请参见本文,了解是否应该为同步方法公开异步包装器

In my eyes PLINQ is mostly useful if you already have a working LINQ query, and want to speed it up because that query would be suited for parallel processing. 在我看来,如果您已经有一个有效的LINQ查询,并且希望加快速度,因为PLINQ适用于并行处理,那么PLINQ最为有用。

What could be the easiest way in your case might be a work queue using 2 threads. 在您的情况下,最简单的方法可能是使用2个线程的工作队列。 I'm pretty sure there are examples of these in the web. 我很确定网络上有这些例子。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM