1

假设我们有一个看起来像这样的模板方法

abstract class Worker
{
  public void DoJob()
  {
    BeforJob()
    DoRealJob();
    AfterJob();
  }

  abstract void DoRealJob();
}

从类继承的子类Wroker应该实现该DoRealJob()方法,当实现在同一个线程下运行时一切正常,方法的三个部分DoJob()按此顺序执行

  1. 工作前()
  2. DoRealJob()
  3. 工作后()

但是当DoRealJob()在另一个线程下运行时,AfterJob()可能会在DoRealJob()完成之前执行

我的实际解决方案是让子类调用AfterJob(),但这并不能防止子类忘记调用它,而且我们失去了模板方法的好处。

尽管DoRealJob()阻塞与否,还有其他方法可以获得一致的调用顺序吗?

4

2 回答 2

1

您无法在代码中同时获得简单的继承(签名和挂钩)和支持异步操作。

这两个目标是相互排斥的。

继承者必须了解直接任务异步)或间接(事件、回调函数Auto(Manual)ResetEvents或其他同步结构)中的回调机制。其中一些是新的,一些是旧的。而且很难说哪一种更适合具体的使用情况。

好吧,看起来有一种简单的多线程代码方法,但是如果您DoRealJob实际上会在另一个进程中运行或使用一些远程作业队列,那么真正的作业甚至会在您的应用程序之外执行呢?

所以:

  • 如果你真的认为你的类将被用作一些异步工作者的基础,那么你应该相应地设计它。
  • 如果不是 - 不要过度设计。你不能考虑任何可能的情况。只需足够好地记录您的课程,我怀疑有人会尝试DoRealJob异步实现,尤其是如果您将其命名为DoRealJobSynchronously. 如果有人试图这样做,那么在这种情况下,你的良心就会非常干净。

编辑:

如果我同时提供 DoRealJob 的同步和异步版本以及标志 IsAsynchronous 以便我可以决定调用哪个版本,您认为这是否正确?

正如我已经说过的,我不知道您的实际使用场景。并且认为设计将能够有效地处理所有这些是不现实的。

还有两个非常重要的问题需要考虑,它们与您的整个Worker班级及其DoJob方法有关:

1)您必须确定该DoJob方法是同步的还是异步的,还是要同时具有同步和异步版本?它与您的问题没有直接关系,但它仍然是非常重要的设计决策,因为它会对您的对象模型产生很大影响。这个问题可以改写为:

您是否希望该DoJob方法在调用它之后阻止任何操作,直到它完成它的工作,或者您是否想将其作为某种StartJob方法调用,这只会启动真正的处理,但它取决于其他机制在工作完成时通知您结束(或手动停止):

        //----------------Sync worker--------------------------
        SyncWorker syncWorker = CreateSyncStringWriter("The job is done");
        Console.WriteLine("SyncWorker will be called now");

        syncWorker.DoJob(); // "The job is done" is written here

        Console.WriteLine("SyncWorker call ended");


        //----------------Async worker--------------------------
        Int32 delay = 1000;
        AsyncWorker asyncWorker = CreateAsyncStringWriter("The job is done", delay);
        Console.WriteLine("AsyncWorker will be called now");

        asyncWorker.StartDoJob(); // "The job is done" won't probably be written here

        Console.WriteLine("AsyncWorker call ended");
        // "The job is done" could be written somewhere here.

2)如果您想DoJob成为异步(或拥有异步版本),您应该考虑是否需要一些机制来通知 DoJob 何时完成处理 -异步编程模式,或者它何时或是否与您完全无关它结束了。

所以:

你有这两个问题的答案吗?

  • 如果是 - 那很好。
  • 如果不是 - 改进并考虑您的要求。
  • 如果您仍然不确定 - 坚持使用简单的同步方法。

但是,如果您认为需要一些基于异步的基础架构,那么考虑到它是 C# 3.0,您应该使用异步编程模型

为什么这是一个而不是基于事件的?因为 IAsyncResult 接口尽管笨重,但非常通用,可以很容易地用于基于任务的模型中,从而简化了未来向更高 .NET 版本的过渡。

它会是这样的:

 /// <summary>
 /// Interface for both  the sync and async job.
 /// </summary>
 public interface IWorker
{
    void DoJob();

    IAsyncResult BeginDoJob(AsyncCallback callback);

    public void EndDoJob(IAsyncResult asyncResult);
}

/// <summary>
/// Base class that provides DoBefore and DoAfter methods
/// </summary>
public abstract class Worker : IWorker
{
    protected abstract void DoBefore();
    protected abstract void DoAfter();


    public IAsyncResult BeginDoJob(AsyncCallback callback)
    {
        return new Action(((IWorker)this).DoJob)
            .BeginInvoke(callback, null);
    }
    //...
}

public abstract class SyncWorker : Worker
{
    abstract protected void DoRealJobSync();

    public void DoJob()
    {
        DoBefore();
        DoRealJobSync();
        DoAfter();
    }
}

public abstract class AsyncWorker : Worker
{
    abstract protected IAsyncResult BeginDoRealJob(AsyncCallback callback);
    abstract protected void EndDoRealJob(IAsyncResult asyncResult);

    public void DoJob()
    {
        DoBefore();
        IAsyncResult asyncResult = this.BeginDoRealJob(null);
        this.EndDoRealJob(asyncResult);
        DoAfter();
    }
}

PS:此示例不完整,未经测试。
PPS:您也可以考虑使用委托代替抽象(虚拟)方法来表达您的工作:

public class ActionWorker : Worker
{
    private Action doRealJob;
    //...

    public ActionWorker(Action doRealJob)
    {
        if (doRealJob == null)
            throw new ArgumentNullException();

        this.doRealJob = doRealJob;
    }

    public void DoJob()
    {
        this.DoBefore();
        this.doRealJob();
        this.DoAfter();
    }
}

DoBefore并且DoAfter可以用类似的方式表达。
PPPS: Action delegate 是 3.5 的构造,因此您可能必须定义自己的接受零参数并返回 void 的委托。

public delegate void MyAction()
于 2014-07-06T19:53:29.483 回答
0

考虑将 DoRealJob 更改为 DoRealJobAsync 并给它一个 Task 返回值。所以你可以等待最终的异步结果。

所以你的代码看起来像

abstract class Worker
{
  public void DoJob()
  {
    BeforJob()
    await DoRealJobAsync();
    AfterJob();
  }

  abstract Task DoRealJob();
}

如果您没有 .net 4.0 并且不想使用旧的 3.0 CTP 异步,您可以使用 normale 任务基础样式:

  abstract class Worker
    {
      public void DoJob()
      {
        BeforJob()
        var task = DoRealJobAsync();
         .ContinueWith((prevTask) =>
              {
                  AfterJob()
               });
      }

      abstract Task DoRealJob();
    }
于 2014-07-06T19:15:24.683 回答