Programming

JustOnce delegate wrapper

I found myself in need of returning a delegate but making sure that the receiver gets to call the delegate at most once.

The task at hand is a concurrent task queue. Threads take a task, and can put back a set of tasks back into the queue if they wish. But if they will put back some tasks, TaskQueue needs to make sure they get to do it only once.

class TaskQueue
{
  public Task Take();
  public void Add(IEnumerable<Task> tasks);
}

Simply the thread would take a job, do its thing, and put the resulting tasks back by calling the TaskQueue.Add method:

class TaskQueue
{
  public Task Take();
  public void Add(IEnumerable<Task> tasks);
}

// thread
Task t = tq.Take();
IEnumerable<Task> newTasks = process(t);
tq.Add(newTasks);

But in this case the thread makes no promise to call it just once. One could write a comment to a future programmer but it’s better if there was a foolproof way. I present you the JustOnce delegate wrapper:

public class JustOnce<Tin>
{
  readonly object callLock = new object();
  bool hasBeenCalledOnce = false;
  readonly Action<Tin> invokee;
  public JustOnce(Action<Tin> del)
  {
    invokee = del;
  }
  public void Invoke(Tin input)
  {
    lock (callLock)
    {
      if (hasBeenCalledOnce)
      {
        throw new Exception("JustOnce: the delegate has already been called once.");
      }
      else
      {
        invokee(input);
        hasBeenCalledOnce = true;
      }
    }
  }
}

The TaskQueue doesn’t expose a public Add method but returns a callback that points to it:

class TaskQueue
{
  ...
  public Task Take(out Action<IEnumerable<Program>> queueCallback)
  {
     queueCallback = new JustOnce<IEnumerable<Program>>(add).Invoke;
     return dequeue();
  }
  Task add(IEnumerable&lt;Task&gt; tasks);
}

Calling thread doesn’t know what JustOnce is:

// thread
Task t = tq.Take(out Action<IEnumerable<Task>> callback);
IEnumerable<Task> newTasks = process(t);
callback(newTasks);

Leave a Reply

Your email address will not be published. Required fields are marked *