如何避免交叉检查

时间:2016-06-09 11:40:33

标签: c# design-patterns

我有几个要运行的工作(伪代码):

public bool IsJob1Running;
public void DoJob1(...) { IsJob1Running = true; ... ; IsJob1Running = false; }

public bool IsJob2Running;
public void DoJob2(...) { IsJob2Running = true; ... ; IsJob2Running = false; }

...

在某些情况下,一次只能运行一个作业,而在其他情况下 - 多个可以运行,但其他运行不应该运行(应该等待或拒绝启动)。所有这些在某种程度上导致了这样的可怕的检查:

if(!IsJob1Running && !IsJob2Running && !IsJob3Running ... ) { ... }

这些突然出现在软件的任何地方:当用户点击按钮(或甚至在禁用按钮之前),开始工作之前,甚至在DoJob内等等。

我讨厌它。想象一下必须添加新Job99的情况。然后软件中的每个地方都必须更新所有检查以包括Job99检查。

我的问题:是否存在一种模式来定义这种交叉检查(关系?),这将允许轻松添加新作业,集中概述所有依赖关系等等?

修改

举个例子:

  

作业1,2,3可以同时运行,但不能在作业4运行时运行(在开始1,2或3之前必须检查作业4是否正在运行,反之亦然)。然后是作业5,6和7,只有一个可以运行,当从作业1中调用作业5时,不应该从作业2中调用它。

3 个答案:

答案 0 :(得分:1)

您可以实现类似基本作业类的内容:

public abstract class BaseJob 
{
    public bool IsRunning { get; private set; }

    public void Run() 
    {
        IsRunning = true;
        RunInner();
        IsRunning = false;
    }

    public abstract void RunInner();
}

然后继承你的所有工作:

public class LoadUserDataJob : BaseJob
{
    public override void RunInner()
    {
        // load user data
    }
}

然后,您将能够获得有关工作的操作列表:

// Check if there is any running task
if (jobsArray.Any(j => j.IsRunning))

// Check if there is a task of type LoadUserDataJob running
// Use Where->Any instead of Single if there are many jobs of this type
if (jobsArray.Where(j => j is LoadUserDataJob).Any(j => j.IsRunning))

您还可以将其与某种Task结合使用,并使用Task.WaitAnyTask.WaitAll以便等待执行。

谈论一个通用框架或模式,它会自动检测和检查作业依赖关系,序列和执行顺序,然后我无法想象 - 它很大程度上取决于您的业务逻辑和工作类型。

答案 1 :(得分:1)

配置作业之间的依赖关系是一个单独的问题,但您可以扩展基类以包含所有作业的静态列表,以及一些描述特定作业应在何种条件下运行的实例属性。例如:

// All instantiated jobs should be added to this list, regardless of whether they're running.
public static List<BaseJob> AllJobs { get; private set; }

// These jobs must be running for the current job to start:
public List<BaseJob> MustBeRunning { get; private set; }

// These jobs must not be running for the current job to start:
public List<BaseJob> CannotBeRunning { get; private set; }

// This overrides the previous two lists to indicate whether the
// job must not run concurrently with any other job (prevents you
// from having to add every other job to "CannotBeRunning":
public bool MustRunIndependently { get; set; }

// Update your run method to take all of this into consideration:
public void Run()
{
    if (MustRunIndependently)
    {
        if (AllJobs.Any(x => x.IsRunning))
        {
            throw new InvalidOperationException("This job must run independently.");
        }
    }
    else if (MustBeRunning.Any(x => !x.IsRunning))
    {
        throw new InvalidOperationException("Required concurrent jobs are not running.");
    }
    else if (CannotBeRunning.Any(x => x.IsRunning))
    {
        throw new InvalidOperationException("Incompatible jobs are currently running.");
    }
    // If we made it here, then it's okay to run the job.
    IsRunning = true;
    RunInner(); // overrided by inheritors to perform actual job work.
    IsRunning = false;
}

答案 2 :(得分:1)

通过实施作业调度程序可以解决此问题。

我接近它的方法是实现一个带有待处理作业队列和当前正在执行的作业列表的调度程序。作业将按到达顺序排列在待处理作业中,并且调度程序将负责检查下一个待处理作业是否有权通过检查当前正在执行的作业并从挂起转移到来运行执行并相应地启动它。

您需要一些回调机制来指示作业何时完成,并应由调度程序从执行列表中清除:

public abstract class Job
{
     public event EventHandler<MyFinishedJobEventArgs> JobFinished;

     public void Run()
     {
         var e = new MyFinishedJobEventArgs(...);
         OnRunJob(e);

         if (JobFinished != null) JobFinished(this, e);
     }

     protected abstract void OnRunJob(MyFinsishedJobEventArgs e); //Job logic goes here.
}

这里的棘手部分当然是正确的性能同步。

确定定义哪些作业可以运行的规则,如果它们都是 JobX在JobY和JobZ正在运行时无法运行的类型,则可以轻松地对其进行管理通过跟踪工作不兼容性在调度程序中;我使用Dictionary<Type, IEnumerable<Type>>

void RegisterIncompatibility(Job job, IEnumerable<Job> incompatibleJobs)
{
     incompatibilities.Add(job.GetType(), incompatibleJobs.Select(j => j.GetType());
} //I prefer registering jobs instead of types to somehow ensure type safety.

现在,调度程序应该在启动下一个作业之前检查是否存在任何当前的不兼容性:

private bool canRunJob(Job job)
{
    //omitted null checks, contains key check, etc.
    if (executingJobs.Any(j => incompatible.Contains(j.GetType())))
       return false;

    return true;
}

只要您保留执行顺序,这应该相当容易。也就是说,第一个入队的作业是第一个要运行的作业,它将阻止所有其他待处理作业,直到允许它运行。

如果您需要跳过当前不允许运行的作业并执行其他待处理作业,直到满足条件,那么事情就会开始变得毛茸茸。