Хорошо, для начала я бы порекомендовал async void EventHandler
.
async eventhandler
с методом await
, чтобы предотвратить заморозку UI / основного потока.Чтобы воспользоваться этой асинхронностью, вам понадобится асинхронное / ожидание в коде.
Предложите изменение в этом:
void Button_Click() {
jobs = new int[] {0,20,10,13,12};
// parallel.foreach...ok great...now all CPUs will
// be loaded in parallel with tasks...
// … But, How to get this to return
// immediately without awaiting completion?
Parallel.ForEach(jobs, job_i => {
DoSomething(job_i);
done.Enqueue(job_i); //tell other thread that job is done
});
// now my gui is blocked and unresponsive until this
// foreach fully completes...instead,
// I want this running in background and returning immediately to gui
// so that its not hung...
}
Кому:
public async void Button_Click()
{
await DoMyWorkAsync();
}
private async Task DoMyWorkAsync()
{
await Task.Run(() =>
{
jobs = new int[] { 0, 20, 10, 13, 12 };
Parallel.ForEach(jobs, job_i =>
{
DoSomething(job_i);
done.Enqueue(job_i);
});
});
}
Примечание: могут быть другие соображения, например, двойной щелчок.Однако, чтобы ответить на исходный вопрос - это должно быть все, что вам нужно.Ниже приведен быстрый и грязный ThreadSafeBoolean.
Для пуристов TAP / C #:
Если бы здесь были Тауб, Клири или Скит, они бы предостерегли меня от оболочки ожидающего задания - однако я обнаружил, что вреальный мир для поддержки шаблонов Async / Await - этот шаблон иногда необходим.Хотя Parallel также поддерживает асинхронные лямбды, их поведение крайне непредсказуемо.Вам понадобится что-то вроде расширений NitroEx или ForEachAsync от Юрия.Однако, если вы хотите запустить что-то вроде этого синхронного запуска и забудет параллельно и асинхронно - асинхронная оболочка задач обычно является самым простым решением.
Ниже я продемонстрирую обработку двойных щелчков с помощью потокового логического значения, поддерживаемого Interlocked.Для более читабельного / обобщенного кода я бы также рассмотрел if (Monitor.TryEnter(myObjectLock, 0))
или SemaphoreSlim(1,1)
.У каждого свой стиль использования.Если бы я придерживался шаблона для этого случая, тогда Monitor.TryEnter
, возможно, самый чистый подход, так как он вернет false, и вы можете выйти, как потокобезопасный bool.Для эффективности и простоты я предполагаю, что мы должны просто пройти через Событие (т.е. ничего не делать), если оно уже запущено.
Образец проверки занятости с Threadsafe Boolean:
using System.Threading;
// default is false, set 1 for true.
private static int _threadSafeBoolBackValue = 0;
public bool ThreadSafeBusy
{
get { return (Interlocked.CompareExchange(ref _threadSafeBoolBackValue, 1, 1) == 1); }
set
{
if (value) Interlocked.CompareExchange(ref _threadSafeBoolBackValue, 1, 0);
else Interlocked.CompareExchange(ref _threadSafeBoolBackValue, 0, 1);
}
}
public async void Button_Click(object sender, EventArgs e)
{
if (!ThreadSafeBusy)
{
ThreadSafeBusy = true;
await DoMyWorkAsync();
ThreadSafeBusy = false;
}
}
private async Task DoMyWorkAsync()
{
await Task.Run(() =>
{
jobs = new int[] { 0, 20, 10, 13, 12 };
Parallel.ForEach(jobs, job_i =>
{
DoSomething(job_i);
done.Enqueue(job_i);
});
});
}
Для других оптимизаций производительностирассмотрите альтернативы параллельным вставкам, и, если рабочая нагрузка интенсивна, ограничьте параллельные foreachs, обрабатываемые одновременно, new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount}
.
private async Task DoMyWorkAsync()
{
await Task.Run(() =>
{
jobs = new int[] { 0, 20, 10, 13, 12 };
Parallel.ForEach(
parallelOptions: new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
source: jobs,
body: job_i =>
{
DoSomething(job_i);
done.Enqueue(job_i);
});
});
}