#c# #multithreading #asynchronous #async-await
#c# #многопоточность #асинхронный #async-await
Вопрос:
Я хочу бесконечно повторять цикл, чтобы проверять все время n = 0 или n = 1
public int check()
{
int n;
Int32 nTest = 0;
nTest = RID.Read(obj);
if (nTest != 0)
{
n = 0;
}
else
{
n = 1;
}
return n;
}
private async void Form1_Load(object sender, EventArgs e)
{
Task<int> task = new Task<int>(check);
task.Start();
while (true)
{
int c = await task;
label7.Text = c.ToString();
}
}
Я пытаюсь установить значение true в Form1_Load для начальной проверки n. когда я запускаю программу, она зависает и ничего не может щелкнуть. Как это исправить?
Комментарии:
1. Прежде всего, как только задача завершена, вы больше не можете ее ожидать. Итак, вам либо нужно каждый раз запускать новую задачу (но не так), либо поместить свой цикл внутрь задачи и оттуда обновлять свой пользовательский интерфейс (в контексте пользовательского интерфейса). Последнее было бы правильным способом. Вам также следует добавить некоторую задержку между циклами. Было бы еще лучше, если бы ваш
RID.Read
тоже был асинхронным.
Ответ №1:
Функция Async-await предназначена для того, чтобы остановить вашу программу от ожидания завершения другого процесса в течение относительно короткого времени. Подумайте об ожидании результатов запроса к базе данных, чтении файла, извлечении данных из базы данных и т.д.
Если вы используете async-await, ваш поток не будет бездействовать, пока другая обработка выполняет запрос. Вместо этого if может осмотреться, чтобы посмотреть, есть ли другие вещи, которые нужно сделать.
Конечно, вы могли бы добиться того же эффекта, создав отдельный поток для выполнения ожидания за вас. Помимо этого, это усложнит ваш код, создание отдельного потока занимает довольно много времени.
Если вы хотите запустить длительный процесс, подумайте о секундах, возможно, минутах или даже дольше, время, необходимое для запуска этого отдельного потока, больше не важно.
Итак, в вашем случае я бы посоветовал использовать BackgroundWorker
Если вашему фоновому рабочему устройству требуется много процедур для выполнения своей работы, рассмотрите возможность получения из BackgroundWorker и начните работать с переопределением BackgroundWorker.OnDoWork
.
В вашем случае backgroundworker нужна только небольшая функция, поэтому будет достаточно подписаться на DoWork
событие.
Используйте свой набор инструментов Visual Studio для добавления backgroundworker или добавьте его вручную в свой конструктор:
// Constructor:
public Form1()
{
InitializeComponent();
// Create a backgroundworker
this.backgroundWorker = new BackgroundWorker
{
// only if you want to display something during processing:
WorkerReportsProgress = true,
WorkerSupportsCancellation = true; // to neatly close your form
};
this.backgroundWorker.DoWork = this.BackgroundProcedure;
this.backgroundWorker.ProgressChanged = this.BackgroundProcessReport;
this.backgroundWorker.RunworkerCompleted = this.BackgroundWorkerFinished;
}
Запуск и остановка просты:
bool IsBackGroundWorkerBusy => this.backgroundWorker.IsBusy;
void StartBackgroundWork()
{
if (this.IsBackGroundWorkerbusy) return; // already started;
this.DisplayBackgroundWorkerActive(); // for example, show an ajax loader
this.backgroundWorker.RunworkerAsync();
// or if you want to start with parameters:
MyParameters backgroundWorkerParameters = new MyParameters(...);
this.backgroundWorker.RunworkerAsync(backgroundWorkerParameters);
}
void RequestCancelBackgroundWork()
{
this.DisplayBackgroundWorkerStopping();
this.backgroundWorker.CancelAsync();
}
Фоновая процедура. Выполняется фоновым потоком.
Вы не можете вызвать какую-либо функцию, связанную с пользовательским интерфейсом, в этой процедуре.
Если вы хотите что-либо обновить в пользовательском интерфейсе, используйте ReportProgress.
void BackGroundProcedure(object sender, DoworkEventArgs e)
{
// if you know that the backgroundworker is started with parameters:
MyParameters parameters = (MyParameters)e.Argument;
// do you work, regularly check if cancellation is requested:
while (!e.Cancel)
{
...
// only if progress reports are needed: report some progress, not too often!
MyProgressParams progressParams = new MyProgressParams(...);
this.ReportProgress(..., progressParams);
}
// if here, the thread is requested to cancel
// if needed report some result:
e.Result = ...;
}
Отчет о ходе работы. Это выполняется вашим потоком пользовательского интерфейса, поэтому при желании вы можете обновить элементы пользовательского интерфейса. Это основная функциональность этого метода.
Первым параметром в отчете о ходе выполнения является число, обычно от 0 .. 100, используемое получателями события выполнения для обновления некоторого визуального отображения прогресса. Если у вас нет никаких указаний на то, сколько времени требуется для выполнения, не используйте значение. Параметрами progressParams может быть любой объект.
void BackgroundProcessReport(object sender, ProgressChangedEventArgs e)
{
// the background worker reported some progress.
... // update UI
}
Runworker Completed вызывается, когда поток завершается. Он содержит данные, присвоенные e.Result. Он выполняется потоком пользовательского интерфейса, поэтому вы можете выполнять любые связанные с пользовательским интерфейсом действия, какие захотите:
void BackgroundWorkerFinished(object sender, RunWorkerCompletedEventArgs e)
{
this.DisplayBackgroundWorkerFinished(); // for example: hide ajax loader
... // use e to process result
}
Аккуратное закрытие вашей формы
Если ваша форма закрывается, фоновая обработка должна быть завершена, прежде чем окно можно будет удалить. Правильный метод — использовать событие OnClosing:
bool closeFormRequested = false;
void OnFormClosing(object sender, CancelEventArgs e)
{
// if background worker busy: request cancellation; can't close the form right now
if (this.IsBackgroundworkerBusy)
{
this.closeFormRequested = true;
e.Cancel = true;
this.RequestCancelBackgroundWork();
}
else
{ // background worker not busy, OK to close
e.Cancel = false;
}
}
Через некоторое время backgroundworker сообщает, что он завершен:
void BackgroundWorkerFinished(object sender, RunWorkerCompletedEventArgs e)
{
this.DisplayBackgroundWorkerFinished();
... // process result
// if requested: close the form:
if (this.closeFormRequested)
this.Close();
}
this.Close()
приведет к OnFormClosing
, но на этот раз фоновый рабочий не будет занят, и закрытие продолжится
Наконец: фоновый рабочий реализует IDisposable, не забудьте удалить его, когда ваша форма будет удалена.
Ответ №2:
Вы должны использовать Microsoft Reactive Framework (он же Rx) — NuGet System.Reactive.Windows.FOrms
и add using System.Reactive.Linq;
— тогда вы сможете это сделать:
private async void Form1_Load(object sender, EventArgs e)
{
IDisposable subscription =
Observable
.While(() => true, Observable.Start(() => RID.Read(obj) == 0 ? 1 : ))
.ObserveOn(this)
.Subscribe(c => label7.Text = c.ToString());
}
Это весь ваш код.
Ответ №3:
когда я запускаю программу, она зависает и ничего не может щелкнуть.
Это потому, что задача запускается только один раз. После завершения оно остается завершенным, поэтому первое await
асинхронно ожидает завершения задачи, но все остальные await
просто возвращают это первое значение, немедленно и синхронно. Вот почему вы ничего не можете щелкнуть: в итоге вы получаете синхронный бесконечный цикл в вашем потоке пользовательского интерфейса.
Чтобы исправить это, я рекомендую использовать Task.Run
для запуска фоновой задачи. Task.Run
гораздо менее подвержен ошибкам, чем конструктор задач и Start
.
private async void Form1_Load(object sender, EventArgs e)
{
while (true)
{
int c = await Task.Run(() => check());
label7.Text = c.ToString();
}
}
В качестве альтернативы, если вам нужен бесконечный цикл в вашем фоновом коде, вы могли бы использовать IProgress<T>
для сообщения о ходе выполнения потоку пользовательского интерфейса:
private Task CheckForever(IProgress<int> progress)
{
while (true)
{
var c = check();
progress?.Report(c);
}
}
private async void Form1_Load(object sender, EventArgs e)
{
var progress = new Progress<int>(c => label7.Text = c.ToString());
await CheckForever(progress);
}
Обратите внимание, что Task.Run
код всегда чище, проще в обслуживании и более типобезопасен, чем BackgroundWorker
код.