У меня есть объект Stream, и я использую BeginRead, чтобы начать чтение (очевидно) в буфер; функция AsyncCallback вызывается после завершения чтения. В рамках этой функции я могу проверить, хочет ли пользователь получить следующий «блок» и снова запустить процесс BeginRead.
Проблема, с которой я столкнулся, заключается в том, что пользователь может отменить чтение во время чтения потока (перед вызовом функции AsyncCallback), так как я могу отменить чтение потока?
Просто для дальнейшего объяснения проблемы - похоже, у меня будет тот же результат, если я буду использовать BackgroundWorker с методом Streams Read или асинхронным методом BeginRead. Пользователь может ждать в течение любого промежутка времени, пока не завершится метод Read / BeginRead, прежде чем я смогу проверить, должен ли поток прекратить чтение.
Редактировать: Приведенный ниже код должен сработать, я в миллионе миль от чего-то приличного в C #, поэтому в нем может быть пара ошибок, так как я сомневаюсь, что он идеален, хотя и делает продемонстрировать решение.
Вкратце, CWorkManager управляет определенным количеством потоков (которые содержатся в классе CWorkerDetail). Каждый CWorkerDetail имеет состояние, которое может быть EWaiting, означающим, что рабочий может быть запущен, EReading, который означает, что рабочий читает из источника, во время которого работник может быть немедленно остановлен, EWriting, который сохраняет данные, которые были прочитаны на диск - это не может быть остановлено немедленно, и этот процесс должен завершиться до остановки потока. Наконец, есть EAborting, который устанавливается менеджером, если работник должен быть прерван как можно скорее; прямо сейчас это устанавливается, только если работник находится в середине чего-то, что не может быть прервано (например, запись на диск).
Прямо сейчас, на самом деле не происходит никакого чтения или записи, так как это только усложнит основное решение (которое в основном является просто функцией StopWorker, проверяющей флаг CWorker, чтобы увидеть, может ли он немедленно прерваться); таким образом, мы просто заставляем поток спать.
Сторона с графическим интерфейсом довольно проста: просто со списком (который показывает статус каждого работника) и кнопкой остановки и запуска. Весь код приведен ниже, надеюсь, это кому-нибудь поможет, но, как я сказал, я не очень разбираюсь в C #, поэтому, пожалуйста, следите за ошибками и т. Д. *
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:
Содержит:
- Поле списка (ListBox_WorkerStatus)
- Кнопка (Button_Start)
- Кнопка (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);
}
}
}
}