Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stopping an asynchronous stream outside of the AsyncCallback function

I've got a Stream object and I am using BeginRead to begin reading (obviously) into a buffer; the AsyncCallback function is called once the reading is complete. Within this function I can check if the user wants to get the next 'block' and start the BeginRead process again.

The problem I have is the user may choose to cancel while the stream is still reading (so before the AsyncCallback function is called), so how can I cancel the reading of the stream?

Just to further explain the issue - it seems I would have the same outcome if I use a BackgroundWorker with the Streams Read method or the asynchronous BeginRead method. The user could be left waiting for any length of time for the Read/BeginRead method to complete before I can check if the stream should stop reading.

Edit: The code below should do the job, I'm a million miles away from being anything decent in C# so it may well have a couple of bugs as I doubt it's perfect, although it does demonstrate the solution.

In brief, the CWorkManager manages a certain number of threads (which are held within a CWorkerDetail class). Each CWorkerDetail has a status, which can be EWaiting meaning the worker can be started, EReading which means the worker is reading from a source, during which time the worker can be stopped instantly, EWriting which saves the data that was read to the disk - this cannot be stoppped instantly and this process must complete before the thread is stopped. Finally there is EAborting which is set by the manager if the worker should be aborted as soon as possible; right now this is only set if the worker is in the middle of something which cannot be interrupted (such as writing to disk).

Right now, there isn't actually any reading or writing going on, as that would just complicate the main solution (which is basically just the StopWorker function checking a flag of CWorker to see if it can abort instantly); as such we simply cause the thread to sleep.

The GUI side is fairly simple with just a listbox (which shows the status of each worker) and a stop and start button. All code is below, hope this helps somebody, but as I say I'm not brilliant with C# so please watch out for bugs etc...

CWorkManager.cs:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;


namespace ThreadApplication {


    //A worker that spawns a number of threads (managed internally) that does nothing useful at all.
    public class CWorkManager {


        //The status of the worker.
        public enum EWorkerStatus {
            EWaiting,
            EReading,
            EWriting,
            EAborting,
        }


        //Holds all data relevant to the worker.
        private class CWorkerDetails {

            //Simple variables.
            private readonly Object _Lock=new Object();
            private Thread gThread;
            private EWorkerStatus gStatus;
            private CWorkManager gParentInstance;
            private int gIndex;

            //Simple constructor.
            public CWorkerDetails(int aIndex, CWorkManager aParentInstance, Thread aThread, EWorkerStatus aStatus) {
                gIndex=aIndex;
                gParentInstance=aParentInstance;
                gThread=aThread;
                gStatus=aStatus;
            }

            //Simple get set methods.
            public Thread GetThread() { lock(_Lock) { return gThread; } }
            public EWorkerStatus GetStatus() { lock(_Lock) { return gStatus; } }

            //Sets the status and automatically updates the GUI.
            public void SetStatus(EWorkerStatus aStatus) {
                lock(_Lock) {
                    gStatus=aStatus;
                    Form1.gInstance.Invoke(new UpdateGUIDelegate(gParentInstance.UpdateGUI), new object[] { gIndex, GetStatus() });
                }
            }

        }


        //Worker variable.
        private List<CWorkerDetails> gWorkers;


        //Simple constructor.
        public CWorkManager(int aWorkerCount){
            gWorkers=new List<CWorkerDetails>();
            for(int tIndex=0; tIndex<aWorkerCount; tIndex++)
                gWorkers.Add(null);
        }


        //Creates and starts the worker.
        public void StartWorker(int aWorkerIndex) {

            //Create a new worker if there is none or if it is waiting to start.
            if(gWorkers.ElementAt(aWorkerIndex)==null||gWorkers.ElementAt(aWorkerIndex).GetStatus()==EWorkerStatus.EWaiting)
                gWorkers[aWorkerIndex]=new CWorkerDetails(aWorkerIndex, this, new Thread(new ParameterizedThreadStart(WorkerMethod)), EWorkerStatus.EWaiting);

            //If the worker is waiting to start, then start.
            if(gWorkers.ElementAt(aWorkerIndex).GetStatus()==EWorkerStatus.EWaiting)
                gWorkers.ElementAt(aWorkerIndex).GetThread().Start(gWorkers.ElementAt(aWorkerIndex));
        }


        //Stops the worker.
        public void StopWorker(int aWorkerIndex) {

            //Do nothing if the worker is null.
            if(gWorkers.ElementAt(aWorkerIndex)==null)
                return;

            //Do nothing if the worker is waiting.
            if(gWorkers.ElementAt(aWorkerIndex).GetStatus()==EWorkerStatus.EWaiting)
                return;

            //If the worker is reading we can abort instantly.
            if(gWorkers[aWorkerIndex].GetStatus()==EWorkerStatus.EReading) {
                gWorkers[aWorkerIndex].GetThread().Abort();
                gWorkers[aWorkerIndex].SetStatus(EWorkerStatus.EWaiting);
                return;
            }

            //Since the worker is not reading or waiting, we have to request the 
            //worker to abort by itself.
            gWorkers[aWorkerIndex].SetStatus(EWorkerStatus.EAborting);

        }


        //Updates the GUI.
        private delegate void UpdateGUIDelegate(int aIndex, EWorkerStatus aStatus);
        private void UpdateGUI(int aIndex, EWorkerStatus aStatus) {
            Form1.gInstance.SetThreadStatus(aIndex, aStatus);
        }


        //This method is where all the real work happens.
        private void WorkerMethod(Object aWorker) {

            //Fetch worker.
            CWorkerDetails mWorker=(CWorkerDetails)aWorker;

            //Loop forever, the thread will exit itself when required.
            while(true) {

                //Is the worker status aborting - if so we stop here.
                if(mWorker.GetStatus()==EWorkerStatus.EAborting) {
                    mWorker.SetStatus(EWorkerStatus.EWaiting);
                    return;
                }

                //This would normally be reading from a stream which would cause the thread
                //to block, simulate this by just sleeping the thread.
                mWorker.SetStatus(EWorkerStatus.EReading);
                Thread.Sleep(3000);

                //Is the worker status aborting - if so we stop here.
                if(mWorker.GetStatus()==EWorkerStatus.EAborting) {
                    mWorker.SetStatus(EWorkerStatus.EWaiting);
                    return;
                }

                //All data has been read, set status to writing and again simulate by
                //sleeping the thread.
                mWorker.SetStatus(EWorkerStatus.EWriting);
                Thread.Sleep(3000);

            }

        }

    }


}

Form1.cs:


Contains:

  • A List box (ListBox_WorkerStatus)
  • A button (Button_Start)
  • A button (Button_Stop)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;


namespace ThreadApplication {


    public partial class Form1:Form {


        public static Form1 gInstance;
        private CWorkManager gManager;


        public Form1() {
            InitializeComponent();
            Button_Start.Click+=new EventHandler(Button_Start_Click);
            Button_Stop.Click+=new EventHandler(Button_Stop_Click);
            gInstance=this;
            for(int tIndex=0; tIndex<5; tIndex++)
                ListBox_WorkerStatus.Items.Add("Created");
            gManager=new CWorkManager(ListBox_WorkerStatus.Items.Count);
        }


        public void SetThreadStatus(int aIndex, CWorkManager.EWorkerStatus aStatus) {
            ListBox_WorkerStatus.Items[aIndex]=aStatus.ToString();
        }


        private void Button_Start_Click(object sender, EventArgs e) {
            if(ListBox_WorkerStatus.SelectedIndex>=0) {
                gManager.StartWorker(ListBox_WorkerStatus.SelectedIndex);
            }
        }


        private void Button_Stop_Click(object sender, EventArgs e) {
            if(ListBox_WorkerStatus.SelectedIndex>=0) {
                gManager.StopWorker(ListBox_WorkerStatus.SelectedIndex);
            }
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
            for(int tIndex=0; tIndex<ListBox_WorkerStatus.Items.Count; tIndex++) {
                gManager.StopWorker(tIndex);
            }
        }


    }


}

like image 759
R4D4 Avatar asked Nov 14 '22 21:11

R4D4


1 Answers

Please Take look atCancel BeginRead this

like image 98
DeveloperX Avatar answered Dec 19 '22 03:12

DeveloperX