Skip to content

Commit 59555d5

Browse files
committed
Новая реализация асинхронной команды
1 parent 718caeb commit 59555d5

1 file changed

Lines changed: 158 additions & 0 deletions

File tree

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
using System.Windows.Input;
2+
3+
using MathCore.Annotations;
4+
5+
namespace MathCore.WPF.Commands;
6+
7+
/// <summary>Абстрактная асинхронная команда с поддержкой отмены и параллельного выполнения</summary>
8+
[PublicAPI]
9+
internal abstract class CommandAsync : Command
10+
{
11+
/// <summary>Список всех активных CancellationTokenSource для отмены и освобождения</summary>
12+
private readonly List<CancellationTokenSource> _ActiveCancellations = [];
13+
14+
#if NET9_0_OR_GREATER
15+
/// <summary>Объект блокировки для синхронизации доступа к спискам</summary>
16+
private readonly Lock _ActiveCancellationsLock = new();
17+
#else
18+
/// <summary>Объект блокировки для синхронизации доступа к спискам</summary>
19+
private readonly object _ActiveCancellationsLock = new();
20+
#endif
21+
/// <summary>Список всех выполняющихся задач</summary>
22+
private readonly List<Task> _ActiveTasks = [];
23+
24+
/// <summary>Выполнять команду в UI-контексте</summary>
25+
public bool ExecuteInUIContext { get; set => Set(ref field, value); } = true;
26+
27+
/// <summary>Дождаться завершения всех выполняющихся задач перед запуском новой</summary>
28+
public bool WaitOne { get; set => Set(ref field, value); }
29+
30+
/// <summary>Отменить все выполняющиеся задачи перед запуском новой</summary>
31+
public bool CancelOtherExecution { get; set => Set(ref field, value); }
32+
33+
/// <summary>Признак длительного выполнения команды</summary>
34+
public bool LongRunning { get; set => Set(ref field, value); }
35+
36+
/// <summary>Команда отмены всех выполняющихся задач</summary>
37+
private CancelExecutionCommand? _CancelCommand;
38+
39+
/// <summary>Команда отмены всех выполняющихся задач</summary>
40+
public ICommand CancelCommand => _CancelCommand ??= new(this);
41+
42+
/// <summary>Вспомогательная команда для отмены всех задач</summary>
43+
private sealed class CancelExecutionCommand(CommandAsync command) : ICommand
44+
{
45+
/// <inheritdoc />
46+
public event EventHandler? CanExecuteChanged;
47+
48+
/// <summary>Вызвать событие изменения возможности выполнения</summary>
49+
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
50+
51+
/// <inheritdoc />
52+
public bool CanExecute(object? p)
53+
{
54+
lock (command._ActiveCancellationsLock)
55+
return command._ActiveCancellations.Any(c => !c.IsCancellationRequested);
56+
}
57+
58+
/// <inheritdoc />
59+
public void Execute(object? p)
60+
{
61+
CancellationTokenSource[] snapshot;
62+
lock (command._ActiveCancellationsLock)
63+
snapshot = command._ActiveCancellations.ToArray();
64+
foreach (var c in snapshot) c.Cancel();
65+
}
66+
}
67+
68+
/// <inheritdoc />
69+
public override bool CanExecute(object? parameter)
70+
{
71+
if (!base.CanExecute(parameter)) return false;
72+
lock (_ActiveCancellationsLock)
73+
return !WaitOne || _ActiveTasks.All(t => t.IsCompleted);
74+
}
75+
76+
/// <inheritdoc />
77+
public override void Execute(object? parameter) => _ = ExecuteInternalAsync(parameter);
78+
79+
/// <summary>Асинхронный запуск выполнения команды</summary>
80+
/// <param name="parameter">Параметр команды</param>
81+
private async Task ExecuteInternalAsync(object? parameter)
82+
{
83+
if (!ExecuteInUIContext || LongRunning)
84+
await Task.Yield().ConfigureAwait(false, LongRunning);
85+
86+
// Отмена всех предыдущих задач, если требуется
87+
if (CancelOtherExecution)
88+
{
89+
CancellationTokenSource[] snapshot;
90+
lock (_ActiveCancellationsLock)
91+
snapshot = _ActiveCancellations.ToArray();
92+
foreach (var c in snapshot)
93+
#if NET8_0_OR_GREATER
94+
await c.CancelAsync();
95+
#else
96+
c.Cancel();
97+
#endif
98+
}
99+
else if (WaitOne)
100+
{
101+
Task[] need_to_wait;
102+
lock (_ActiveCancellationsLock)
103+
need_to_wait = _ActiveTasks.Where(t => !t.IsCompleted).ToArray();
104+
if (need_to_wait.Length > 0)
105+
try { await Task.WhenAll(need_to_wait).ConfigureAwait(false); } catch { /* */ }
106+
}
107+
108+
// Создаём новый CTS для текущей задачи
109+
var cts = new CancellationTokenSource();
110+
lock (_ActiveCancellationsLock)
111+
_ActiveCancellations.Add(cts);
112+
var cancel = cts.Token;
113+
114+
Task main_task = null;
115+
try
116+
{
117+
OnBeforeExecuted(parameter);
118+
main_task = ExecuteAsync(parameter, cancel);
119+
lock (_ActiveCancellationsLock)
120+
_ActiveTasks.Add(main_task);
121+
await main_task.ConfigureAwait(false);
122+
OnExecuted(parameter);
123+
}
124+
catch (OperationCanceledException) { /* */ }
125+
catch (Exception ex) { if (OnError(ex)) return; throw; }
126+
finally
127+
{
128+
lock (_ActiveCancellationsLock)
129+
{
130+
_ActiveCancellations.Remove(cts);
131+
_ActiveTasks.Remove(main_task);
132+
}
133+
DisposeCts(cts);
134+
_CancelCommand?.RaiseCanExecuteChanged();
135+
}
136+
return;
137+
138+
static void DisposeCts(CancellationTokenSource cts)
139+
{
140+
try { cts.Dispose(); } catch { /* */ }
141+
}
142+
}
143+
144+
/// <summary>Отменить все выполняющиеся задачи</summary>
145+
private void CancelInternal()
146+
{
147+
CancellationTokenSource[] snapshot;
148+
lock (_ActiveCancellationsLock)
149+
snapshot = _ActiveCancellations.ToArray();
150+
foreach (var c in snapshot) c.Cancel();
151+
}
152+
153+
/// <summary>Асинхронное выполнение команды</summary>
154+
/// <param name="parameter">Параметр команды</param>
155+
/// <param name="Cancel">Токен отмены</param>
156+
/// <returns>Задача выполнения команды</returns>
157+
public abstract Task ExecuteAsync(object? parameter, CancellationToken Cancel);
158+
}

0 commit comments

Comments
 (0)