Can I design any lock-free solution for this scena

2019-06-14 23:30发布

问题:

I have a simple Employee class as follows

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

I then have a ProcessEmployees class which do concurrent processing of these employees in method called ProcessThisEmployee. Within this method, I have to call a third party library method. So far its all simple. Problem is sometime when user choose to cancel this operation when its in progress, I need to do some clean up on any ThirdPartyLibrary class instances that haven't completed processing yet. Note I don't have any control on the ThirdPartyLibrary class and it does not have any mechanism for cancellation an existing task. It does provide a Clean method that I can call on any instances where call to SomeAPI hasn't finished yet. As a result, I am maintaining an local List of all instances. When user choose to cancel operation, I call CleaupIfUserCancelOperation method of my class that Clean method of this third party library instance that are in the my local list. Below is my code for this.

class ProcessEmployees
{
    private List<Employee> _Employees;
    List<ThirdPartyLibrary> libraries = new List<ThirdPartyLibrary>();
    private object sync = new object();
    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()
    {

        Task[] tasks = this._Employees.AsParallel().Select(x => this.ProcessThisEmployee(x)).ToArray();
        Task.WaitAll(tasks);
        // other stuff
    }

    private async Task ProcessThisEmployee(Employee x)
    {
        ThirdPartyLibrary library = new ThirdPartyLibrary();
        lock (sync)
        {
            libraries.Add(library);
        }
        await Task.Factory.StartNew(() => library.SomeAPI(x) );

        lock (sync)
        {
            libraries.Remove(library);
        }
    }

    private void CleaupIfUserCancelOperation()
    {
        foreach (ThirdPartyLibrary library in libraries)
            library.Clean();
    }
}

I have to lock libraries instance twice in here which of course is a performance hit. Can there be any lightweight (lock-free) mechanism that could be used in this case? I don't know much about the concurrent collection, wonder if those could be of any use in this scenario?

回答1:

The two lock calls very likely have much less performance impact than the rest you are doing. Assuming they do you can use ConcurrentDictionary to add and remove instances (you shouldn't use a List anyway because right now you have quadratic performance overhead in the number of concurrent libraries).

It's probably better to make each async method cancel itself and cleanup its own stuff. Pass them a CancellationToken. Use Register to register a callback that cleans up the library. There is really no need for global state/collections here.