【问题标题】:Is it a reasonable approach for lock-free design for this scenario对于这种情况,无锁设计是一种合理的方法吗
【发布时间】:2015-01-11 14:07:10
【问题描述】:

这是对我之前的一个问题here 的跟进。总之,我试图为这种情况提出一个无锁设计,在取消任务时我想调用第三方库的方法。在回答我的问题时,一位乐于助人的 SO 参与者建议使用 CancellationToken.Register,但我不确定在哪里以及如何在此处使用它。下面是我想出的代码。如果您发现此方法有任何问题,或者是否有更好的替代方案可以解决此问题,请告诉我。

class ProcessEmployees
    {
        private List<Employee> _Employees;
        CancellationTokenSource cs = new CancellationTokenSource();

        public  ProcessEmployees()
        {
            _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" }
            };
        }

        public void StartProcessing()
        {
            try
            {
                Task[] tasks = this._Employees.AsParallel().WithCancellation(cs.Token).Select(x => this.ProcessThisEmployee(x, cs.Token)).ToArray();
                Task.WaitAll(tasks);
            }
            catch (AggregateException ae)
            {
                // error handling code
            }
            // other stuff
        }

        private async Task ProcessThisEmployee(Employee x, CancellationToken token)
        {
            ThirdPartyLibrary library = new ThirdPartyLibrary();
            if (token.IsCancellationRequested)
                token.ThrowIfCancellationRequested();
            await Task.Factory.StartNew(() => library.SomeAPI(x) );
            if (token.IsCancellationRequested)
            {
                library.Clean();
                token.ThrowIfCancellationRequested();
            }
        }
    }

【问题讨论】:

  • 为什么要在操作完成之后检查取消?假设大部分处理时间都花在SomeAPI() 方法中,那么在ProcessThisEmployee() 方法中检查取消有什么意义呢?除了您可以找到的具体建议之外,例如How to: Cancel a PLINQ Query是你找的吗?

标签: c# .net-4.0 locking task-parallel-library async-await


【解决方案1】:

您的流程代码可以简化为以下(这里是您如何使用Register)

private void ProcessThisEmployee(Employee x, CancellationToken token)
{
    ThirdPartyLibrary library = new ThirdPartyLibrary();
    token.ThrowIfCancellationRequested();
    using(token.Register(() => library.Clean())
    {
        library.SomeAPI(x);
    }
    token.ThrowIfCancellationRequested(); //Not sure why you cancel here in your original example.
}

如果令牌在using 语句的范围内被取消,它将调用library.Clean(),如果之后调用它不会调用该函数。我也摆脱了你的Task.Run,没有理由为你正在做的事情浪费额外的线程。最后我去掉了额外的if (token.IsCancellationRequested) 检查,ThrowIfCancellationRequested() 有如果检查自身内部,你不需要事先检查。

另外,因为在我的简化中,您不再返回任务,所以您的 StartProcessing 代码变为

    public void StartProcessing()
    {
        try
        {
            this._Employees.AsParallel().WithCancellation(cs.Token).ForAll(x => this.ProcessThisEmployee(x, cs.Token));
        }
        catch (AggregateException ae)
        {
            // error handling code
        }
        // other stuff
    }

使用ForAll( 代替Select(

【讨论】:

  • 谢谢。一个问题。假设我想在我的查询表达式中包含 WithDegreeOfParallelism,容量也为 2。假设我在 List 中有 5 个项目,假设在前 2 个操作正在进行时令牌被取消。在这种情况下,根本不会尝试最后 3 项,还是我需要在此查询表达式或代码中添加一些额外的内容?
  • WithCancellation 将使它甚至不启动最后 3 个。这就是该修饰符的作用,它不会影响已经启动的项目(这就是为什么您必须放入自己的取消代码跨度>
  • 我想问的一件事是,现在您没有等待调用 library.SomeAPI,因此这将成为阻塞调用。您删除它的任何特殊原因?
  • 之前也是一个阻塞调用(有点)。您已经在来自AsParallel 调用的线程池线程上,因此没有理由通过Task.Run 调用创建额外的线程池线程。此外,您在 start 方法中执行了阻塞调用Task.WaitAll(tasks),导致代码在那里阻塞,我只是在前一行使代码阻塞。如果您在原始程序中使用了await Task.WhenAll(tasks),我就不会改变这种行为。
  • 我明白了。感谢您花时间解释它
猜你喜欢
  • 2016-01-06
  • 1970-01-01
  • 2023-03-10
  • 1970-01-01
  • 2017-12-11
  • 2012-10-29
  • 1970-01-01
  • 2012-01-21
相关资源
最近更新 更多