【问题标题】:IJob implementation class Execute method how to use the lock keyword?IJob实现类Execute方法如何使用lock关键字?
【发布时间】:2017-06-26 22:32:34
【问题描述】:

背景:我需要使用定时任务扫描数据表(1分钟扫描一次,或者30秒扫描一次),数据表会增加记录,数据表在第三个数据源,以collection作为参数做一个Thing,这个事情需要的时间无法确定(一个http请求),完成后修改每个数据库记录的状态,避免下次再次扫描当查询出来时。

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

由于多个job触发时间较短,DoWork()方法执行时间不可预测,可能会导致多个线程同时访问persions变量。如何使用 lock 语句来处理这个问题?

【问题讨论】:

  • 注意:Execute()中有三个地方用到了中间变量process
  • 你只想lock (personListLock) { /* ... */ }?你可以在this question 上看到它是如何工作的。请注意,如果这是一个,很可能您的问题将作为重复项关闭
  • @lcepickle 我希望这段代码在多线程中正常工作。谢谢!
  • @Icepickle 感觉我的问题和你给出的参考有点不一样,我需要知道在我的整个代码环境中,如何使用lock来保证多线程的安全,逻辑是正确的。 (可能我的代码之前需要调整一下,但是怎么调整呢?)

标签: c# quartz.net


【解决方案1】:

我对处理集合的三个访问独立于一个类

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

修改了ScanJob.cs文件(不再直接使用process集合,而是通过BaseProcessOperator&lt;T&gt;

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

【讨论】:

    【解决方案2】:

    您可以通过添加 DisallowConcurrentExecution 属性来禁止 Job 的并发执行

    [DisallowConcurrentExecution]
    public class ScanJob : IJob
    {
    
    }
    

    【讨论】:

    • 感谢您让我知道这个属性!
    • 我找到了另一种方式。 var task = Task.Factory.StartNew(); task.Wait();
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-26
    • 2014-01-18
    • 2020-05-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多