I had tried background worker, thread and task but the procedure are so long to get a solution according to my question. suppose I use a task, I can't add any textboxes in ABC function which is called in button_click. When I use textbox inside button click function on the place of ABC function it's worked. But this process is not fulfil my requirement. If I called this function directly into buttonclick event it's hang my UI. Here is the example code of ABC function.
public void ABC()
{
While(true)
{
richTextBox1.AppendText("Hello");
richTextBox2.AppendText("Tesing");
}
}
If anyone have an idea to achieve this, please tell me.
I am trying this because my sensor gives me data on continuous basis and I used these values to draw a graph, so I tried to run that in background to update my graphs on continuous basis.
CodePudding user response:
I believe that there is a better way, but sometimes you can't just change what is already there… In those cases, I use the Producer/Consumer Pattern for problems that are similar to yours.
1) Create the ProducerConsumer class.
public class ProducerConsumer<T>
{
private readonly BlockingCollection<T> mCollection = new BlockingCollection<T>(100);
private readonly Action<T> mConsumer;
private readonly object SYNC_LOCK = new object();
public ProducerConsumer(Action<T> consumer) {
mConsumer = consumer;
}
public void Produce(T obj) {
mCollection.Add(obj);
}
// If using multiple producers
public void ThreadSafeProduce(T obj) {
lock (SYNC_LOCK ) {
Produce(obj);
}
}
private void Loop() {
while (true) {
var obj = mCollection.Take();
if (obj == null) return;
mConsumer(obj);
}
}
public void Start() {
Task.Run(() => Loop());
}
}
2) Example of how to use it
public partial class Form1 : Form
{
private ProducerConsumer<string> mProducerConsumer;
public Form1()
{
InitializeComponent();
mProducerConsumer = new ProducerConsumer<string>(Consumer);
mProducerConsumer.Start();
FakeSensorReadings();
}
private void FakeSensorReadings()
{
Task.Run(() =>
{
while (true)
{
mProducerConsumer.Produce("Teste string\r\n");
Thread.Sleep(100);
}
});
}
private void Consumer(string str) {
// Run on Main Thread
this.Invoke(new MethodInvoker(() =>
{
textBox1.Text = str;
}));
}
}
HINT
You can use any object type that you want, like int, float, struct, custom objects, etc.
CodePudding user response:
So you have a Sensor class that produces data, and you have another class that processes (= consumes) the produced data. Sometimes the Producer produces more data than the Consumer can process, other times the Consumer has enough time to process the produced data.
A problem like this screams for the Producer-Consumer pattern: a Producer produces data that a Consumer processes in a different tempo: sometimes the Producer is faster, sometimes the Consumer is faster.
For this, Microsoft came up with the Dataflow Task Parallel Library (TPL). You can download the code using the nuget package manager.
Let's assume your Sensor produces a stream of SensorData, sometimes faster, sometimes slower.
Your Consumer process needs to take this Sensor data and process it. While this data is being processed, the Sensor might produce new SensorData. This SensorData needs to be buffered.
This buffering is done using the TPL BufferBlock. Whenever your Sensor produces some SensorData it Adds the data to the BufferBlock, and starts Producing the next SensorData.
Meanwhile, the Consumer does nothing but wait until there is some SensorData in the BufferBlock. As soon as it arrives, it removes it from the BufferBlock and processes the SensorData. When finished processing, it waits until there is SensorData again.
So if the Producer is (temporarily) faster than the Consumer, then there will be several SensorData in the BufferBlock. If the Consumer is faster, the BufferBlock will regularly be empty.
This process stops when the Sensor knows that there is no more data. It notifies the BufferBlock. When all SensorData in the BufferBlock are processed, the Consumer is notified that no more data is to be expected.
class SensorData {...};
class Sensor
{
// send Produced SensorData to this ITargetBlock:
public ITargetBlock<SensorData> DataBuffer {get; set;}
private SensorData ReadSensor() {...}
public async Task ProduceDataAsync(CancellationToken token)
{
// TODO: check if this.TargetBuffer and token not null
while (!token.IsCancellationRequested)
{
SensorData sensorData = this.ReadSensor();
await this.TargetBuffer.SendAsync(token);
}
// if here: cancellation requested. Stop producing SensorData
// notify that no data will be produced anymore
this.TargetBuffer.Complete();
}
}
The Consumer:
class Consumer
{
// the Consumer will listen for data here:
public ISourceBlock<SensorData> DataBuffer {get; set;}
private void ProcessSensorData(SensorData sensorData) {...}
public async Task ConsumeDataAsync()
{
// TODO: check if DataBuffer
// as long as there is SensorData expected: wait for it and process
while (await this.DataBuffer.OutputAvailableAsync())
{
// There is some data; fetch it and process it
SensorData sensorData = await this.DataBuffer.ReceiveAsync();
this.ProcessSensorData(sensorData);
}
// if here: no more SensorData expected
}
}
Putting it all together
BufferBlock<SensorData> buffer = new BufferBlock<SensorData>();
Sensor sensor = new Sensor
{
DataBuffer = buffer,
}
Consumer consumer = new Consumer
{
DataBuffer = buffer,
}
Now everything is ready to be started. For instance when pressing a button:
async void OnButtonStart_Clicked(object sender, ...)
{
this.buttonStart.Enabled = false; // to prevent starting again
this.buttonStop.Enabled = true; // so we can stop the process
await this.StartProcessingAsync();
this.buttonStop.Enabled = false;
this.buttonStart.Enabled = true;
}
private CancellationTokenSource CancellationTokenSource {get; set;}
async Task StartProcessingAsync()
{
using (this.CancellationTokenSource = new CancellationTokenSource())
{
CancellationToken token = this.CancellationTokenSource.Token;
// start Producing and Consuming:
Task producerTask = sensor.ProduceDataAsync(token);
Task consumerTask = consumer.ConsumeDataAsync();
// continue producing and consuming until both tasks are finished
await Taks.WhenAll(new Task[] {producerTask, consumerTask});
}
}
To stop the process:
async void StopButton_ClickedAsync(object sender, ...)
{
this.NotifyProducerBeingCancelled(); // notify the operator
// Start the cancellation process
this.CancellationTokenSource.Cancel();
}
Cancelling the CancellationTokenSource will notify all Tokens from this Source. The Producer has such a token. It will check regularly whether cancellation is requested. If so, it will stop producing SensorData and notify the Buffer that no data is to be expected anymore. The method will return.
Meanwhile the Consumer is happily consuming SensorData. If all Produced SensorData has been processed, it sees that no more data is to be expected. The method will return.
Method StartProcessingAsync was waiting until both tasks ProduceDataAsync and ConsumeDataAsync are finished. When both are finished it will Enable and Disable the buttons again and return.
If the operator clicks the Stop Button several times before the Producer and Consumer are stopped, then only the CancellationTokenSource is cancelled again, which won't do anything.
Note that this whole thing is done by one thread. Or to be more accurate: only one context is used, this means that it is as if only one thread does everything. Therefore there is no need for you to protect using mutexes or semaphores. No need for InvokeRequired in method ProcessSensorData, because the thread has the UI context, and thus can update UI elements.
If the update of the UI elements was only as an example in your question, and you don't need to update UI elements at all, but for instance only save data in a file, consider to add ConfigureAwait(false) in your await statements
The Producer is
