1

I am new to using TPL. I've been able to implement a simple post / receive transaction on a BufferBlock but when I try to go asynchronous, I get hung.

Here is a simplified version of what I am trying.

Declare the message buffer (made it global)

Dim msgBuffer As New BufferBlock(Of String)

A simple function to post the messages which is just a list of file names in a directory

Private Sub PostMessagesToBuffer()

    filesList = Directory.GetFiles(txtBoxSrcFilesDir.Text, fileFilter).ToList
    For Each file In filesList
        msgBuffer.Post(file)
    Next
    msgBuffer.Complete()
End Sub

I created this function for processing the messages:

Private Async Function ProcessMessagesAsync() As Task(Of Integer)

    Dim msgsProcessed = 0

    While Await msgBuffer.OutputAvailableAsync
        Dim msg = msgBuffer.Receive
        Console.WriteLine(msg)
        msgsProcessed += 1
    End While

    Return msgsProcessed

End Function

Then just this set of calls to run it all.

Dim msgProcessor = ProcessMessagesAsync()
PostMessagesToBuffer()
msgProcessor.Wait()
Console.writeLine("Processed " & msgProcessor.Result & " Messages.")

I can debug and see the messages getting added to the buffer but the "While Await" never gets the signal that a message is available on the queue. It just sits there, never getting into the loop to do the work on the message. Am I missing something rather simple here?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Rob M
  • 55
  • 1
  • 5
  • It is a clear case of deadlock, the code cannot get past the While loop to arrive at PostMessagesToBuffer(). This normally happens on another thread. – Hans Passant Dec 10 '19 at 21:38
  • What kind of application is this? Console application? Windows Forms? – Theodor Zoulias Dec 10 '19 at 22:21
  • It’s a windows forms application. – Rob M Dec 10 '19 at 23:47
  • That makes sense. Your code shouldn't deadlock in a Console application. But the Windows Forms applications install a synchronization context in the UI thread, to force the async workflow to continue in the UI thread after an `await`, which is very helpful but introduces the possibility of deadlocks if the UI thread becomes blocked for some reason. – Theodor Zoulias Dec 11 '19 at 08:47

1 Answers1

2

You are mixing synchronous and asynchronous code. Deadlocks are pretty common when blocking on async code. Instead of Wait:

msgProcessor.Wait()

...it is safer to Await:

Await msgProcessor

Also the DataflowBlock.Receive is a blocking method. The proper method to use after awaiting the DataflowBlock.OutputAvailableAsync is the method DataflowBlock.TryReceive.

I should also note that retrieving manually the elements of a DataflowBlock is feasible but uncommon. Usually the last block in a dataflow pipeline is an ActionBlock, that doesn't generate output.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • I’ll give that a try and see if that helps. I did try to switch to using ActionBlock attached to a processing method. That seems to work fine. – Rob M Dec 10 '19 at 23:48
  • You could take a look at [this](https://stackoverflow.com/questions/58714155/tpl-how-do-i-split-and-merge-the-dataflow/58751948#58751948) answer where I implemented an extension method `ToListAsync` very similar to yours `ProcessMessagesAsync`, using the method `TryReceive` instead of `Receive`. – Theodor Zoulias Dec 11 '19 at 08:41