1

Background: I need to use a scheduled task to scan a data table (1 minute scan once, or maybe 30 seconds once), the data table will increase the record, the data table in the third source of data, with the collection as a parameter to do a Thing, the time required for this matter can not be determined (an http request), after the completion of the revised state of each database records, to avoid the next scan again when the query out.

public class ScanJob : IJob
{
    //Simulation of the data table, the reality of his records will not increase.
    public static List<Person> persions = new List<Person>
    {
        new Person() { Name = "aaa", Status = true },
        new Person() { Name = "bbb", Status = true },
        new Person() { Name = "ccc", Status = true },
        new Person() { Name = "ddd", Status = true },
        new Person() { Name = "eee", Status = true },
    };

    //Intermediate variable, to avoid the previous has not yet ended, the next time has begun
    public static List<string> process = new List<string>();
    public void Execute(IJobExecutionContext context)
    {
        //Equivalent to a database query
        var pers = persions.Where(s => s.Status).ToList();
        //Exclude the object that was executed the previous time
        pers = pers.Where(s=> !process.Contains(s.Name)).ToList();

        Action<List<Person>> doWork = (s) =>
        {
            process.AddRange(s.Select(n => n.Name));//Add to intermediate variable
            DoWork(s);//Do something that can not be expected time (http request)
        };

        doWork.BeginInvoke(pers, (str) =>
        {
            //After DoWork() ends, organize the intermediate variables
            if (pers != null && pers.Count() > 0)
            {
                foreach (var i in pers)
                    process.Remove(i.Name);
            }
        }, null);

        Console.ReadKey();
    }

    public void DoWork(List<Person> _pers)
    {
        Thread.Sleep(1000 * 60 * 1 + 1000 * 10);//Simulate http requests (One minute 10 seconds)

        var firstPer = persions.Where(s => s.Status).FirstOrDefault();
        if (firstPer != null)
        {
            //Simulation to modify the table record
            firstPer.Status = false;
        }
    }
}

Because multiple job triggers are shorter, and the DoWork () method execution time is unpredictable, it may cause multiple threads to access the persions variable at the same time. How can I use the lock statement to handle this problem?

Icepickle
  • 12,689
  • 3
  • 34
  • 48
qingyun1029
  • 45
  • 1
  • 9
  • **Note**: There are three places in Execute () that use the intermediate variable **process** – qingyun1029 Jun 25 '17 at 16:50
  • You just want to `lock (personListLock) { /* ... */ }`? You can see how this works on [this question](https://stackoverflow.com/questions/6029804/how-does-lock-work-exactly). Just a note, if this is the one, it could well be possible your question will be closed as a duplicate – Icepickle Jun 25 '17 at 16:53
  • @lcepickle I hope this code works correctly in multithreading. Thanks ! – qingyun1029 Jun 26 '17 at 01:26
  • @Icepickle I feel my problem and you give a little different reference, I need to know in my entire code environment, how to use lock to ensure that multi-threaded security, logic is correct. (Maybe my code needs to be adjusted before, but how to adjust it?) – qingyun1029 Jun 26 '17 at 01:46

2 Answers2

0

I put three accesses to the processing collection independent of a class

public static class BaseProcessOperator<T>
{
    static List<string> prevProcess = new List<string>();
    static object obj = new object();
    public static void AddRange(List<string> para)
    {
        lock (obj)
        {
            prevProcess.AddRange(para);
        }
    }

    public static List<string> GetProcesses()
    {
        lock (obj)
        {
            return prevProcess;
        }
    }

    public static void Remove<TParam>(List<TParam> currList, Func<TParam, string> fn)
    {
        if (currList != null && currList.Count() > 0)
        {
            lock (obj)
            {
                foreach (var i in currList)
                {
                    var r = prevProcess.FirstOrDefault(s => s == fn(i));
                    if (!string.IsNullOrWhiteSpace(r))
                        prevProcess.Remove(r);
                }
            }
        }
    }
}

Modified ScanJob.cs file(No longer directly use the process set, but through the BaseProcessOperator<T>)

public void Execute(IJobExecutionContext context)
{
    //Equivalent to a database query
    var pers = persions.Where(s => s.Status).ToList();
    //Exclude the object that was executed the previous time
    pers = pers.Where(s => !BaseProcessOperator<ScanJob>.GetProcesses().Contains(s.Name)).ToList();

    Action<List<Person>> doWork = (s) =>
    {
        BaseProcessOperator<ScanJob>.AddRange(s.Select(n => n.Name).ToList());//Add to intermediate variable
        DoWork(s);//Do something that can not be expected time (http request)
    };

    doWork.BeginInvoke(pers, (str) =>
    {
        //After DoWork() ends, organize the intermediate variables
        BaseProcessOperator<ScanJob>.Remove(pers, (s) => { return s.Name; });
    }, null);

    Console.ReadKey();
}
qingyun1029
  • 45
  • 1
  • 9
0

You may disallow concurrent execution of Job by adding DisallowConcurrentExecution attribute

[DisallowConcurrentExecution]
public class ScanJob : IJob
{

}
Set
  • 47,577
  • 22
  • 132
  • 150