The past couple of days I've learned a lot from this site about the Producer/Consumer pattern and how to implement it with the help of .NET's BlockingCollection.
However all of the examples I've seen assume the consumer task performs to completion whatever it needs to do to the queue items as it takes them and then immediately moving on to the next available item.
In my situation the item taken from the queue is being presented to the user for manipulation/interaction until they indicate they are done at which point the consumer is free to take the next available item.
I've mocked up a simple example of what I'm trying to do that will hopefully illustrate it better:
public partial class Form1 : Form
internal class Job
public string Id;
public string Data1;
public string Data2;
public string Data3;
BlockingCollection<Job> jobList = new BlockingCollection<Job>();
// Task option 1:
// Can't access UI (easily)
workTask = Task.Factory.StartNew(() => ProcessJobs());
// Task option 2:
// Blocks the UI thread while waiting for
var uiContext = TaskScheduler.FromCurrentSynchronizationContext();
workTask = Task.Factory.StartNew(() => ProcessJobs(),
CancellationToken.None, TaskCreationOptions.None, uiContext);
private async void ProcessJobs()
currentJob = jobList.Take();
workDone = new TaskCompletionSource<bool>();
private void btnCreateNewJob_Click(object sender, EventArgs e)
incomingJob = new Job();
incomingJob.Id = jobNumber++.ToString();
private void btnObtainJobData_Click(object sender, EventArgs e)
if (incomingJob != null)
// Pretend this data isn't static
incomingJob.Data1 = incomingJob.Id + " Data1";
incomingJob.Data2 = incomingJob.Id + " Data2";
incomingJob.Data3 = incomingJob.Id + " Data3";
incomingJob = null;
private void FillData()
txtData1.Text = currentJob.Data1;
txtData2.Text = currentJob.Data2;
txtData3.Text = currentJob.Data3;
private void btnFinishCurrentJob_Click(object sender, EventArgs e)
// Do something with the edited data
And here is what the form looks like to help visualize this (contrived) workflow:
The workflow is as follows:
While the operator is interacting with the job data they are allowed to create additional jobs and acquire the data for each without interfering with the current job. The reason steps 1 and 2 are separate is because in the actual application there is an intervening verification step that needs to be done before obtaining job data.
Different problems arise depending on how the Task is created in the Form1.Initialize() event. Option 1 means I can't interact with the UI easily. I'll need to implement marshalling code (which I will do if that's the best way but I feel like modern C# probably has the tools to deal with this more elegantly). Option 2 results in the UI thread blocking while waiting for the BlockingCollection to Take the next job from an empty queue so that is unusable.
I suspect there's a better way to implement this which is why I'm posting here. Is there a well-known variation of the producer/consumer pattern that will meet my needs? Or maybe another pattern altogether?