Небольшая шаблонная конструкция для обработки “тяжелого” кода в отдельном потоке и уведомления о прогрессе выполнения основного потока программы.
Да, я отдаю себе отчет в том, что такое может и/или должно решаться иначе в мире .Net, но.. фиг с ним.
В WPF-программах можно взаимодействовать с пользовательским интерфейсом только из основного потока, поэтому методы с обработкой результата нужно вызывать именно “оттуда”, например, путем использования Dispatcher.Invoke(). Для этого в конструкцию необходимо передать параметр типа DispatcherObject, дальний потомок которого является класс Window.
TParamCollection, TParamItem и TResult – это типы коллекции, элементов коллекции и результата обработки этой коллекции соответственно.
Последовательность вызова методов шаблона такова:
1) PreUI() – перед началом обработки коллекции. Тут можно включить какой-нибудь ProgressBar или анимашку.
2) Run() – для непосредственной обработки элементов.
2.1) PreItemUI() – перед обработкой элемента.
2.2) PostItemUI() – после обработки элемента (с результатом типа bool). Увеличиваем % в ProgressBar.
3) PostUI() – после обработки всей коллекции (с результатом типа TResult). Выключаем ProgressBar.
4) StopUI() – при вызове метода Stop() вызовется этот. Также выключаем ProgressBar.
Методы с суффиксом UI будут запускать в основном потоке.
Для запуска всей этой херни:
1) создать наследника AsyncItemsTask и переопределить UI-методы.
2) создать экземпляр класса AsyncItemsTask, с передачей параметра с формой.
3) запустить метод Start(), с передачей самой коллекции.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
public abstract class AsyncItemsTask<TDispatcher, TParamCollection, TParamItem, TResult> where TDispatcher : DispatcherObject { public const int CLOSE_WAIT_TIME = 500; // хз почему 500! protected TDispatcher form; // форма protected Thread thread; // поток /* * Методы для переопределения примерно в порядке их вызова */ protected virtual void PreUI() { } protected abstract TResult Run(TParamCollection param1); protected virtual void PreItemUI(TParamItem param2, int index) { } protected virtual void PostItemUI(TParamItem param2, bool oneRes, int index) { } protected virtual void PostUI(TResult res) { } protected virtual void StopUI() { } public AsyncItemsTask(TDispatcher form) { this.form = form; } /* * Запуск фонового потока */ public void Start(TParamCollection collection) { RunInUIThread((() => PreUI())); this.thread = new Thread(() => { // Run() - главный обработчик объектов коллекции TResult allRes = Run(collection); RunInUIThread((() => PostUI(allRes))); }); this.thread.Start(); } /* * Перед обработкой очередного объекта коллекции */ public void PreItem(TParamItem param2, int index) { RunInUIThread((() => PreItemUI(param2, index))); } /* * После обработки очередного объекта коллекции */ public void PostItem(TParamItem param2, bool oneRes, int index) { RunInUIThread((() => PostItemUI(param2, oneRes, index))); } /* * Принудительная остановка потока */ public void Stop() { if (thread != null && thread.IsAlive) { // ожидаем завершение потока, иначе рубим if (!thread.Join(CLOSE_WAIT_TIME)) { thread.Abort(); } } StopUI(); } /* * Обертка для запуска функции callback в основном потоке программы */ private void RunInUIThread(Action callback) { form.Dispatcher.Invoke(callback); } } |
Может использоваться, например, так (где MainWindow – объект типа Window):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
public class ReadAllTask : Utils.AsyncItemsTask<MainWindow, Controller, VisualParameter, GroupOperationResult> { public ReadAllTask(MainWindow form) : base(form) { } protected override void PreUI() { form.AddLog("Чтение параметров..", LogTypes.Info); form.SetInProcessMode(ProcessModes.ReadAll); form.Controller.TrySetBusy(Controller.OperationTypes.ReadAll); } protected override GroupOperationResult Run(Controller controller) { int index = 0; var res = new GroupOperationResult(true, 0); foreach (var param in Templater.ParamsCollection) { PreItem(param, index); bool readOneRes = controller.ReadValue(param); if (!readOneRes) { // не останавливаемся, если ошибка AccessDenied или UndefParamInfo if (!(controller.LastAnswer == ControllerAZT20.Answers.AccessDenied.Value || controller.LastAnswer == ControllerAZT20.Answers.UndefParamInfo.Value)) { PostItem(param, readOneRes, index++); res.IsSuccess = false; res.ErrorParam = param; return res; } } else res.SuccessCount++; PostItem(param, readOneRes, index++); } return res; } protected override void PreItemUI(VisualParameter param, int index) { form.ShowReadOneStart(param, index); } protected override void PostItemUI(VisualParameter param, bool res, int index) { form.ShowReadOneStatus(param, res, index); } protected override void PostUI(GroupOperationResult res) { form.Controller.SetNoBusy(); form.ShowReadAllResult(res); form.SetInProcessMode(ProcessModes.None); } protected override void StopUI() { form.Controller.SetNoBusy(); form.AddLog("Остановка чтения параметров", LogTypes.Warning); form.SetInProcessMode(ProcessModes.None); } } |
Вспомогательный класс GroupOperationResult для возврата детального результата:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class GroupOperationResult { public bool IsSuccess; public int SuccessCount; public Parameter ErrorParam; public GroupOperationResult(bool success, int count) { this.IsSuccess = success; this.SuccessCount = count; } } |
Кстати, есть класс таймера System.Windows.Threading.DispatcherTimer, callback-код которого выполняется в рамках потока пользовательского интерфейса и без всяких Dispatcher.Invoke(), тут си-шарперы подумали за нас.