1

I'm using a BlockingCollection to create a queue where multiple threads can add items to, which are processed by a separate thread. The added item also contains a callback function (delegate) which is called after the item is processed. To add the item I use the TryAdd method.

This is all working fine, but know I'm wondering if there is any way to await the processing after running the TryAdd. What I want is, that the adding Thread waits for the processing to be done and then continues.

I hope that I described my problem well, so that anyone can give me a hint.

My target framework is Mono/.Net4.5.

olydis
  • 3,192
  • 13
  • 28
Sebastian
  • 315
  • 3
  • 10
  • 6
    Just out of curiosity: why are you trying to wait? If you need to process an item syncronously, why are you using a `BlockingCollection`? You could just process the item at the call site and not add it to the collection. Maybe I'm missing something here. – xxbbcc Sep 01 '15 at 14:18
  • I suspect this is duplicate of [convert callback-based async method to awaitable task](http://stackoverflow.com/questions/11879967/best-way-to-convert-callback-based-async-method-to-awaitable-task). – Alexei Levenkov Sep 01 '15 at 14:30
  • @AlexeiLevenkov: how is this `TryAdd` callback based? – olydis Sep 01 '15 at 14:47
  • @olydis I I think OP mention collection just to describe the code. I think whole question can be condensed to "item contains a callback function which is called after the item is processed ... await ... for the processing to be done". Could be completely wrong - if OP provided code it would be clear whether it is case (and I'd close as dup instead of commenting). – Alexei Levenkov Sep 01 '15 at 14:58
  • You're right, my queue item is a struct containing the data to be processes and a callback function. It would be nice to have the possibility for both, synchronous and asynchronous processing. Since the data are commands which are send to a external device, sometimes I need to wait for the response before I continue and sometimes the response doesn't matter and will just be added to a logfile. – Sebastian Sep 02 '15 at 11:55

1 Answers1

1

The only solution is to coöperate - the processing side must signal you that the item is processed. Implementation of this is quite easy - one way would be this:

public struct SignalizableItem<T>
{
  private readonly T _value;
  private readonly TaskCompletionSource<object> _signaller;

  public SignalizableItem(T value, TaskCompletionSource<object> signaller)
  {
    _value = value;
    _signaller = signaller;
  }

  public void Process(Action<T> action)
  {
    try
    {
      action(_value);
      _signaller.SetResult(default(object));
    }
    catch (Exception ex)
    {
      _signaller.SetException(ex);
    }
  }
}

public static class BlockingCollectionExtensions
{
  public static Task QueueAndWaitAsync<T>
     (this BlockingCollection<SignalizableItem<T>> @this, T value)
  {
    var tcs = new TaskCompletionSource<object>();
    @this.Add(new SignalizableItem<T>(value, tcs));
    return tcs.Task;
  }
}

The usage is quite simple - on the producer side, you simply do

await collection.QueueAndWaitAsync(value);

On the consumer side, you'll unwrap the value and signal when ready:

var item = collection.Take();

item.Process
 (
   data =>
   {
     // Your processing
     ...
   }
 );

And of course, the collection will be BlockingCollection<SignalizableItem<YourType>> instead of BlockingCollection<YourType>.

You could further simplify the processing by adding another extension method:

public static void Process<T>
  (this BlockingCollection<SignalizableItem<T>> @this, Action<T> action)
{
  @this.Take().Process(action);
}

It might also be a good idea to implement cancellation (a simple CancellationToken should work fine) or another form of shutdown.

Something actually usable could end up with

public static void ProcessAll<T>
  (this BlockingCollection<SignalizableItem<T>> @this, Action<T> action, 
   CancellationToken cancellationToken)
{
  SignalizableItem<T> val;
  while (@this.TryTake(out val, -1, cancellationToken)) val.Process(action);
}

abstracting away the whole processing mechanism, and exposing just the simple action delegate.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • In which Namespace do I find the `Unit` Type? – Sebastian Sep 02 '15 at 11:52
  • 1
    @Sebastian Oh, my bad - I usually use `Unit` when not wanting to pass a type, but I tried to remove it from the sample; just replace it with `object`. – Luaan Sep 02 '15 at 12:07