当前位置: 动力学知识库 > 问答 > 编程问答 >

c# - Producer/Consumer pattern where consumer must wait for UI interaction

问题描述:

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:

using System;

using System.Collections.Concurrent;

using System.Threading;

using System.Threading.Tasks;

using System.Windows.Forms;

namespace BlockingCollectionTest

{

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>();

int jobNumber;

Job currentJob;

Job incomingJob;

Task workTask;

TaskCompletionSource<bool> workDone;

public Form1()

{

InitializeComponent();

// Task option 1:

// Can't access UI (easily)

workTask = Task.Factory.StartNew(() => ProcessJobs());

// Task option 2:

// Blocks the UI thread while waiting for

// BlockingCollection.Take()

var uiContext = TaskScheduler.FromCurrentSynchronizationContext();

workTask = Task.Factory.StartNew(() => ProcessJobs(),

CancellationToken.None, TaskCreationOptions.None, uiContext);

}

private async void ProcessJobs()

{

while (!jobList.IsAddingCompleted)

{

currentJob = jobList.Take();

FillData();

workDone = new TaskCompletionSource<bool>();

await workDone.Task;

}

}

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";

jobList.Add(incomingJob);

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

workDone.SetResult(true);

}

}

}

And here is what the form looks like to help visualize this (contrived) workflow:

User Form

The workflow is as follows:

  1. User creates new job
  2. User obtains job data
  3. Job is added to job list
  4. ProcessJobs takes job from list and fills in form
  5. User does stuff with the job data and clicks button when done

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?

分享给朋友:
您可能感兴趣的文章:
随机阅读: