Форум программистов, компьютерный форум, киберфорум
C#: WPF, UWP и Silverlight
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.67/18: Рейтинг темы: голосов - 18, средняя оценка - 4.67
1341 / 920 / 265
Регистрация: 08.08.2014
Сообщений: 2,775

Долгие асинхронные операции и очень быстрый пользователь

14.12.2021, 17:43. Показов 4536. Ответов 36
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Минимальный пример:
1. В UI есть поле ввода.
2. Пользователь вносит изменения в данные. Срабатывает событие.
3. По событию нужно:
3.1. Отправить асинхронный запрос на сервер.
3.2. По полученным результатам обновить часть свойств VM (забинденных на View). В т.ч. несколько коллекций смерджить.

И пункты 3.1-3.2 могут быть относительно долгими. И пока они не завершились, пользователь может ещё что-нибудь ввести и спровоцировать повторный вызов 3.1-3.2. Более того, технически возможно, что обработчик от первого срабатывания завершится позже второго. Т.е. вариант - не трогать общие коллекции, а просто по завершению подменять целевую тоже не подходит как решение (да и View на подобные подмены плохо реагирует).

Добавлять задержку и инициировать событие только после окончания ввода смысла не имеет. Это лишь снизит количество срабатываний п.3 при быстром непрерывном вводе.

Блокировать ввод до окончания обработки - плохая идея, интерфейс станет неотзывчивым и станет вести себя странно (например, пользователь поставил фокус в combobox и стрелками клавиатуры жмёт вверх/вниз много раз подряд, если при этом combobox будет сериться/блокироваться, это будет неприятно для пользователя).

В общем виде решение сейчас выглядит следующим образом - если один обработчик уже запущен, то:
1. Все последующие просто ставятся в очередь.
2. Когда первый обработчик завершается, из очереди берётся последний, запускается на обработку, а все остальные просто выбрасываются (события не содержат полезной нагрузки, обработчик события ориентируется только на актуальное состояние VM, потому их можно отбросить).

Проблему это решает. Но в данном случае при помощи велосипеда (самописный менеджер, который эти события собирает, идентифицирует, группирует, корректно отбрасывает лишние и отдаёт в обработку последнее после завершения предыдущего).

Вопросы:
1. Можно ли эту проблему решить через дефолтный функционал ReactiveUI? Сразу оговорюсь, что 'Throttle' тут не поможет, он для другого, он именно для реализации задержки после окончания ввода.
2. Может есть уже какие-то готовые/проверенные паттерны/подходы для этого? Глобальные шины/аггрегаторы событий тут не особо подходят, т.к. это всё же внутренняя кухня VM, которой нужно как-то разрулить ввод пользователя и результаты обработки данных в Модели.
3. Может у меня вообще подход неправильный?

Добавлено через 19 минут
немного подробностей

Пример и варианты того, что не подходит.

Панель с параметрами для ввода/выбора, например, панель фильтра - выбирается дата, вводится какой-то текст, выбирается статус из комбобокса и ставится какой-то чекбокс.

И по всему этому набору параметров надо с сервера запросить данные и отобразить их в таблице (это простой пример, в реальности там может быть сложный апдейт нескольких связанных коллекций для отображения).

Типовые варианты решения:
1. Пользователь ввёл все параметр, нажал кнопку "загрузить", все элементы ввода стали не активными (и кнопка тоже), данные обновились, элементы снова стали доступны. Этот вариант не подходит, нужно именно в режиме живого ввода табличные данные с сервера обновлять.
2. Без кнопки "загрузить" при изменении любого параметра делать то же самое - деактивировать, загружать, активировать. Не подходит по той же причине, UI будет мигать и вести себя некрасиво.
3. При любых изменениях запускать в фоне процесс загрузки и как-то управлять повторными событиями. Этот вариант подходит с точки зрения UI, именно его я пытаюсь сейчас реализовать, именно про него основные вопросы, т.к. пока "костыли и велосипеды".
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
14.12.2021, 17:43
Ответы с готовыми решениями:

Unattend.xml и очень долгие обновления
Сразу скажу, что глупых вопросов задавать не буду, и даже на ответ не надеюсь... Вопрос таков что придётся описать всю хронологию....

Асинхронные операции на C#
Реализую обмен данными с устройством по USB-каналу (bulk передача) с использованием функций WinUSB API. Хочу реализовать асинхронное...

Асинхронные операции
Есть у меня источник с элементами (ObservableCollection), и специальные коллекции "отображения", тоже типа ObservableCollection....

36
1341 / 920 / 265
Регистрация: 08.08.2014
Сообщений: 2,775
15.12.2021, 16:17  [ТС]
Студворк — интернет-сервис помощи студентам
И у вас Andrey-MSK, и у вас limeniye неожиданное для меня использование DI-контейнера.

Получается, что VM имеет к нему доступ через статик-класс? Т.е. VM внутри себя может запросить любой нужный ей сервис.

Есть какие-то преимущества у такого подхода?

Я просто всегда считал, что VM должна через конструктор определять свой контракт и, соответственно, получать все необходимые ей зависимости исключительно через конструктор.
0
 Аватар для Andrey-MSK
3397 / 2283 / 388
Регистрация: 14.08.2018
Сообщений: 7,763
Записей в блоге: 4
15.12.2021, 16:22
kotelok, вот полностью BaseVM, она имеет ссылку на слой Services, который вращается вообще отдельно от всего
C#
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
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
 
using DALibrary.Interfaces;
 
using Services;
using Services.Interfaces;
 
namespace VModels
{
    public class BaseViewModel : IBaseViewModel
    {
        private readonly IBackgroundTaskQueue _taskQueue = ServiceLocator.Instance.GetService<IBackgroundTaskQueue>();
        private readonly IMainDA _mainDA = ServiceLocator.Instance.GetService<IMainDA>();
        private readonly Lazy<IWindowService> _windowService
            = new Lazy<IWindowService>(() => ServiceLocator.Instance.GetService<IWindowService>());
 
        protected IBackgroundTaskQueue TaskQueue => _taskQueue;
        protected IMainDA MainDA => _mainDA;
        protected IWindowService WindowService => _windowService.Value;
 
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
Добавлено через 2 минуты
А вот ServiceLocator, который многие считают антипаттерном
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.Collections.Generic;
 
namespace Services
{
    public class ServiceLocator
    {
        private static ServiceLocator _instance;
        private readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();
        private ServiceLocator() { }
 
        public static ServiceLocator Instance => _instance ?? (_instance = new ServiceLocator());
        public ServiceLocator Register<T>(T service)
        {
            _services[typeof(T)] = service;
            return this;
        }
        public T GetService<T>() => (T)_services[typeof(T)];
    }
}
Добавлено через 38 секунд
Хотя постоянно его используют в WPF
2
1341 / 920 / 265
Регистрация: 08.08.2014
Сообщений: 2,775
15.12.2021, 16:26  [ТС]
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
Хотя постоянно его используют в WPF
Ну вот давно не использую, потому удивился, увидев его сразу в двух примерах в этой теме.
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
15.12.2021, 16:36
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
limeniye, в мессенджерах можно и так, а вот в программах для учета такое не прокатит
Да, но и не все птицы летают. Возьмите в пример пингвинов.
0
 Аватар для Andrey-MSK
3397 / 2283 / 388
Регистрация: 14.08.2018
Сообщений: 7,763
Записей в блоге: 4
15.12.2021, 16:37
Цитата Сообщение от kotelok Посмотреть сообщение
Я просто всегда считал, что VM должна через конструктор определять свой контракт и, соответственно, получать все необходимые ей зависимости исключительно через конструктор.
Это вы имеете ввиду сторонние DI-контейнеры типа Unity, Autofac и иже с ними?
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
15.12.2021, 16:39
Цитата Сообщение от kotelok Посмотреть сообщение
что VM должна через конструктор определять свой контракт
Я как-то работал в компании, пришёл на легаси проект. Там таки было. А когда узнал про DI -- жить стало проще.
А то, помню, ссылки от App.xaml.cs через 20 классов пробрасывал
1
1341 / 920 / 265
Регистрация: 08.08.2014
Сообщений: 2,775
15.12.2021, 16:55  [ТС]
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
Это вы имеете ввиду сторонние DI-контейнеры типа Unity, Autofac и иже с ними?
Технически, подойдёт любой, который умеет через конструктор инжектить. Мне пока хватает дефолтного 'Microsoft.Extensions.DependencyInjectio n.ServiceProvider'.

Добавлено через 1 минуту
Цитата Сообщение от limeniye Посмотреть сообщение
А то, помню, ссылки от App.xaml.cs через 20 классов пробрасывал
Да, без DI-контейнера передавать через конструктор смысла не имеет, т.к. тогда приходится в VM передавать все зависимости, которые ей не нужны, но могут потребоваться любой вложенной VM, или любой вложенной в неё.
2
 Аватар для Andrey-MSK
3397 / 2283 / 388
Регистрация: 14.08.2018
Сообщений: 7,763
Записей в блоге: 4
15.12.2021, 17:02
Цитата Сообщение от kotelok Посмотреть сообщение
Мне пока хватает дефолтного 'Microsoft.Extensions.DependencyInjectio n.ServiceProvider'.
Можете показать примерчик? Хочу попробовать уйти от ServiceLocator, но чет никак не соображу во что читать Примеров по MVVM + DI не нашел подходящих, или может плохо искал...

Добавлено через 3 минуты
kotelok, Просто в моём случае я получаю все ссылки в BaseVM, и потом уже во всех VM использую что надо, а тут получится под каждую VM инжектить своё, или как-то можно через BaseVM решить?
0
Фрилансер
 Аватар для FateOri
761 / 458 / 146
Регистрация: 06.01.2017
Сообщений: 4,856
15.12.2021, 17:34
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
А вот ServiceLocator, который многие считают антипаттерном
это дикий хак который в проде лучше не использовать))
но я честно признаюсь что примерно такой подход только в чуть болеее надежном виде иногда применяю когда лениво тянуть полновесные библиотеки а результат нужен уже вчера))

Цитата Сообщение от kotelok Посмотреть сообщение
Да, без DI-контейнера передавать через конструктор смысла не имеет, т.к. тогда приходится во VM передавать все зависимости, которые ей не нужны, но могут потребоваться любой вложенной VM, или любой вложенной в неё.
в точку)
когда проект крупный без этого никак а то такая вермишель для вложенных VM образуется
тогда и начинаешь ценить DI))
0
1341 / 920 / 265
Регистрация: 08.08.2014
Сообщений: 2,775
15.12.2021, 18:39  [ТС]
Andrey-MSK
Простейший вариант. В реальной реализации метод 'Create' (фабрики) содержит больше логики по начальной инициализации VM, например, после конструктора вызывает цепочку виртуальных методов разных этапов инициализации VM, которые могут быть переопределены при реализации VM, и в которых можно достаточно тонко разделить этапы начальной инициализации - предзагрузка справочников и заполнение комбобоксов/списков, загрузка начального состояния из внешнего сервиса, инициализация подписок на события, установка значений по умолчанию.

Единственная очевидная проблема при таком подходе - в метод 'Create' (и в конструктор VM) не получится передать какие-то данные для начальной инициализации (вернее можно, но строгую типизацию у меня сделать не получается даже через дженерики). Пока что это обходится через передачу в Create колбэка (анонимного), который вызывается до окончания метода Create, но после конструктора и части встроенной инициализации VM. И там можно для экземмпляра VM вызвать метод 'Load(itemId)' или выполнить любую другую кастомную настройку. Кривовато и надо помнить, но других вариантов пока не нашёл.

Сам ServiceProvider нигде внутри VM не используется. Возможно, это уже вопрос заморочек на перфекционизм, но мне лично комфортнее, когда я по конструктору сразу вижу все зависимости. Тесты на VM не делал, но говорят, что и тестировать их попроще в этом случае, т.к. можно просто в конструктор выдать только то, что реально нужно этой VM.

P.S.: ошмётки ReactiveUI в коде опциональны, на DI он никак не влияет.

Пример VM
C#
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
public abstract class ViewModelBase : ReactiveUI.ReactiveObject
{
}
 
public class SomeViewModel : ViewModelBase
{
    private readonly IViewModelFactory<OtherViewModel> _otherViewModelFactory;
    private readonly SampleModel _sampleModel;
 
    [ReactiveUI.Fody.Helpers.Reactive]
    public OtherViewModel NestedViewModel { get; set; }
 
    public SomeViewModel(
        IViewModelFactory<OtherViewModel> otherViewModelFactory,
        SampleModel sampleModel)
    {
        _otherViewModelFactory = otherViewModelFactory;
        _sampleModel = sampleModel;
    }
 
    public void AnyHanlder()
    {
        NestedViewModel = _otherViewModelFactory.Create();
    }
}
ViewModelFactory
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface IViewModelFactory<T> where T : ViewModelBase
{
    T Create();
}
 
sealed class ViewModelFactory<T> : IViewModelFactory<T> where T : ViewModelBase
{
    private readonly IServiceProvider _serviceProvider;
 
    public ViewModelFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
 
    public T Create()
    {
        return _serviceProvider.GetRequiredService<T>();
    }
}
Startup приложения

C#
1
2
3
4
5
6
7
8
9
10
11
var sc = new ServiceCollection();
sc.AddSingleton(typeof(IViewModelFactory<>), typeof(ViewModelFactory<>));
 
//Регистрация в 'sc' всех моделей, сервисов, фабрик http-клиентов и т.п. ...
//....
 
//Построение SP, инициализация и отображение главного окна.
var sp = sc.BuildServiceProvider();
var rootViewModel = sp.GetRequiredService<IViewModelFactory<RootViewModel>>().Create();
var viewManager = sp.GetRequiredService<IViewManager>();
viewManager.Show(rootViewModel);


Добавлено через 7 минут
Пример вызова расширенного Create с колбэком (тут async-версия)
C#
1
2
3
4
5
6
7
    var someViewModel = await _someViewModelFactory.Create((vm) =>
    {
        vm.Load(itemId, someOptions);
    });
 
    Tabs.Add(someViewModel);
    SelectedTabItem = someViewModel;
2
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16166 / 11286 / 2892
Регистрация: 21.04.2018
Сообщений: 33,176
Записей в блоге: 2
15.12.2021, 21:57
Цитата Сообщение от kotelok Посмотреть сообщение
Но зачем вообще нужна модель и её события для типовых форм в тонком клиенте?
Я же написал, что сильно не вникал в вопрос.
В тонком клиенте обычно вообще нет Модели.
Собственно, поэтому он и "тонкий".
Иногда некую часть VM, работающую с внешним API, выделяют в "Локальную Модель".
Но по сути это просто удобное название для определённой части функционала общего слоя VM.
1
1341 / 920 / 265
Регистрация: 08.08.2014
Сообщений: 2,775
17.12.2021, 16:10  [ТС]
В общем, предварительный рабочий вариант выглядит следующим образом (по сути то, что Элд Хасп предлагал, но с учётом прочих нюансов/сценариев).

Суть - любые обработчики, которые ...

1. Могут занимать какое-то время (как правило минимальное, но сервер/сеть может и призадуматься).
2. Не должны блокировать UI-поток.
3. Конкурируют за ресурсы VM (т.е. даже два разных обработчика могут конкурировать, модифицируя одну и ту же коллекцию и при этом очень не хочется внутри каждого задумываться про явные блокировки).

... ставятся в общую внутреннюю очередь VM и выполняются последовательно с возможностью скипнуть последовательный блок однотипных обработчиков (skipSame = true).

Как оформить это на тасках и 'ContinueWith' разобраться не смог, т.к. для этого нужно слишком глубоко во внутреннее устройство этого механизма вникать. Так что используется запускаемый явным образом поток, так я хотя бы понимаю когда он стартует/завершается и могу там корректно исключение поймать/подавить (если сам обработчик этого не делает).

Все обработчики пока что 'async Task', т.к. они могут использоваться и напрямую в некоторых сценариях, где нужен 'await' на них (может потом будут другие варианты и надо будет под них модифицировать).

Возможно, кто-нибудь что-нибудь подскажет по улучшению/упрощению/ошибкам этой реализации.

C#
1
2
3
4
5
6
7
8
9
10
11
sealed class HandlerInfo
{
    public Func<Task> Handler { get; }
    public bool SkipSame { get; }
 
    public HandlerInfo(Func<Task> handler, bool skipSame)
    {
        Handler = handler;
        SkipSame = skipSame;
    }
}
C#
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
public abstract class ViewModelBase : ReactiveUI.ReactiveObject 
{
    private readonly Queue<HandlerInfo> _handlersQueue = new Queue<HandlerInfo>();
    private Thread _handlersThread = null;
 
    //Добавление обработчика в очередь и запуск потока, если ещё не запущен.
    protected void EnqueueTask(Func<Task> handler, bool skipSame = true)
    {
        lock (_handlersQueue)
        {
            _handlersQueue.Enqueue(new HandlerInfo(handler, skipSame));
 
            if (_handlersThread == null)
            {
                _handlersThread = new Thread(HandersThreadProc);
                _handlersThread.Start();
            }
        }
    }
 
    //Обработка очереди до её опустошения.
    private void HandersThreadProc()
    {
        while (true)
        {
            //Выбор следующего обработчика и пропуск лишних.
            var nextHandlerInfo = default(HandlerInfo);
            lock (_handlersQueue)
            {
                if (_handlersQueue.Count == 0)
                {
                    _handlersThread = null;
                    break;
                }
 
                nextHandlerInfo = _handlersQueue.Dequeue();
 
                if (nextHandlerInfo.SkipSame)
                {
                    while (_handlersQueue.Count > 0 && _handlersQueue.Peek().Handler.Method == nextHandlerInfo.Handler.Method)
                    {
                        _handlersQueue.Dequeue();
                    }
                }
            }
 
            //Выполнение.
            nextHandlerInfo.Handler().Wait();
        }
    }
}
C#
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
//Примеры использования.
public sealed class SomeViewModel : ViewModelBase
{
    private async Task TestTask()
    {
        Debug.WriteLine("TestTask start");
        await Task.Delay(4000);
        Debug.WriteLine("TestTask end");
    }
 
    //V1
    protected override void InitObserve()
    {
        this.WhenAnyValue(vm => vm.DocumentType, vm => vm.StartDate)
            .Throttle(TimeSpan.FromMilliseconds(100))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ => EnqueueTask(TestTask));
    }
 
    //V2
    private void SomeUiEventHandler()
    {
        EnqueueTask(TestTask);
    }
 
    //V3
    public async Task DoOther()
    {
        //....
        await TestTask();
        //...
    }
}
1
Фрилансер
 Аватар для FateOri
761 / 458 / 146
Регистрация: 06.01.2017
Сообщений: 4,856
17.12.2021, 17:34
пару ссылок по теме

https://stackoverflow.com/ques... -method-is

https://stackoverflow.com/ques... ect=1&lq=1

когда у меня была похожая задача я скомбинировал примитивы доступные ReactiveUi примерно так как в одном из ответов из первой ссылки
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16166 / 11286 / 2892
Регистрация: 21.04.2018
Сообщений: 33,176
Записей в блоге: 2
18.12.2021, 09:49
Цитата Сообщение от kotelok Посмотреть сообщение
Как оформить это на тасках и 'ContinueWith' разобраться не смог
Пример.
C#
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
namespace System.Threading.Tasks
{
    /// <summary>Класс выполняющий только одну последнюю задачу.</summary>
    public class OneRunningTask
    {
        private Task task; // Задача с исполняемым методом или методом, который нужно выполнить.
        private readonly object locker = new();
 
        /// <summary>Постановка метода в очередь на исполнение.</summary>
        /// <param name="action">Делегат метода.</param>
        /// <returns><see langword="true"/>, если не было другого исполняемого метода
        /// и этот метод начал сразу исполняться.<br/>
        /// <see langword="false"/> - какой-то метод исполняется и метод из
        /// <paramref name="action"/> ставится в очередь на исполнение.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="action"/> == null.</exception>
        /// <remarks>Если до окончания исполнения предыдущего метода, метод <see cref="Push(Action)"/>
        /// будет вызван ещё раз, то он заменит метод из очереди и тот не будет исполняться.</remarks>
        public bool Push(Action action)
        {
            if (action == null)
                throw new ArgumentNullException(nameof(action));
 
            Task newTask = new(action);
            newTask.ContinueWith(ContinueWith);
 
            lock (locker)
            {
                bool isFirst = task == null;
                task = newTask;
                if (isFirst)
                {
                    task.Start();
                    return false;
                }
                else
                {
                    return true;
                }
            }
        }
 
        // Метод продолжения задачи.
        // Если в task находится не завершённая задача,
        // то она запускается на выполнение.
        private void ContinueWith(Task comletedTask)
        {
            lock (locker)
            {
                if (task == comletedTask)
                {
                    task = null;
                }
                else
                {
                    task.Start();
                }
            }
        }
    }
}
Применение:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
        private static void PrintNum(int num)
        {
            Console.WriteLine($"Begin run #{num}.");
            Thread.Sleep(2500);
            Console.WriteLine($"Finish run #{num}.");
        }
 
        static void Main(string[] args)
        {
            OneRunningTask oneRunning = new();
            for (int i = 0; i < 10; i++)
            {
                oneRunning.Push(() => PrintNum(i));
                Thread.Sleep(1000);
            }
            Thread.Sleep(5000);
            Console.ReadLine();
Консоль:
Code
1
2
3
4
5
6
7
8
9
10
Begin run #0.
Finish run #0.
Begin run #2.
Finish run #2.
Begin run #4.
Finish run #4.
Begin run #7.
Finish run #7.
Begin run #9.
Finish run #9.
1
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
18.12.2021, 12:28
Консоль:
У меня вот немного другая задача: по таймеру запускается метод.
В методе есть запрос, который нужно awaitить.
Результат запроса я обрабатываю тут .
В итоге поднимается событие с какими-то результатом.
События приходят хаотично (не последовательно).

Поэтому в событии я указываю его порядковые номер.
Если у меня пришло событие с номером 0, а потом событие с номером 2 — значит событие 1 ещё не дошло.
Обрабатываю следующим способом:
ссылка доступна только для ЭлдХасп
C#
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
    public class SyncMethod
    {
        private int oldNumber = -1;
 
        public async Task WaitingAnotherMethodsAsync(int number, Func<Task> action, int milliseconds = 10000)
        {
            int timeout = 0;
            int еxpectedNumber = number - 1;
 
            while (oldNumber < еxpectedNumber && timeout < milliseconds)
            {
                timeout++;
                await Task.Delay(1);
            }
 
            if (oldNumber < еxpectedNumber)
            {
                throw new ArgumentException($"Error message: \"Message after the {oldNumber} was lost.\"");
            }
            else if (oldNumber > еxpectedNumber)
            {
                throw new ArgumentException($"Error message: \"Message after the {oldNumber} already finished.\"");
            }
            else
            {
                await action();
 
                oldNumber++;
                if (oldNumber != number)
                    throw new ArgumentException("Error message: \"Something wrong ;(((.");
            }
        }
    }
Можно ли как-то это синхронизировать без переменных с порядком номером? Может есть уже готовые способы?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16166 / 11286 / 2892
Регистрация: 21.04.2018
Сообщений: 33,176
Записей в блоге: 2
18.12.2021, 12:39
Цитата Сообщение от limeniye Посмотреть сообщение
Можно ли как-то это синхронизировать без переменных с порядком номером? Может есть уже готовые способы?
С событиями в асинхронных методах всегда беда в этом отношении.
В WEB используются штампы времени.
Но я реально сталкивался когда с одним штампом приходит несколько событий и в неверной последовательности.
Допустим, добавить элемент, изменить элемент.
А первым приходит изменить.
А элемента ещё нет и кого менять непонятно.
Выкинуть тоже нельзя.
Так как потом придёт "добавить" и оно будет с неактуальными данными.
Приходится вводить приоритеты событий, очередь обработчиков, ожидание в этой очереди и т.д.

На мой взгляд, надёжнее и проще номер события.
1
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
18.12.2021, 13:24
Элд Хасп, вот как, хорошо, принял. Спасибо!
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
18.12.2021, 13:24

Асинхронные операции
Console.WriteLine(&quot;Some code&quot;); using (var fs = new System.IO.FileStream(&quot;File.txt&quot;, System.IO.FileMode.Open)) ...

Асинхронные операции
Всем привет, имею следующий вопрос. Допустим, есть форма или JFrame, на которой есть кнопка и прогрессбар. При нажатии на кнопку...

Асинхронные операции
Добрый день! Подскажите, пожалуйста, почему у меня блокируется основной поток при асинхронной операции.. При открытии окна, я...

Синхронные/асинхронные операции
Подскажите примерный паттерн для приостановки скажем бесконечного цикла, который уже запущен каким либо событием с формы. Используя async...

Как работают асинхронные операции в Qt
Хочу понять механику. Вот я вызываю QTcpSocket::connectToHost(...). Вызов неблокирующий. Через какое-то времы посылается сигнал...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
37
Ответ Создать тему
Новые блоги и статьи
Запустил конкурс "тем и промптов для текстовых квестов созданных почти чисто ИИ"
Adler 06.10.2026
Всем привет! За последние три-четыре дня я создал более 16 текстовых квестовых игр используя преимущественно по одному запросу к ИИ на игру. Мне так понравилось смотреть все ветки/ сцены во всех. . .
ИИ не может найти нужный язык в списке
Supersumestria 05.10.2026
Я ему даю вот такое изображение и прошу найти и подчеркнуть немецкий язык. Возвращает он вот это: https:/ / i. **********/ vqBWLe2. png Нужную строчку в 3й колонке просто выдумал. . Это. . .
Новая последняя моя музыка в SUNO
zorxor 05.10.2026
Здравствуйте, дорогие мои друзья! С большой радостью я хотел бы представить вам свою новую последнею музыку, которую сгенерировала мне по моей просьбе нейросеть SUNO. С уважением, zorxor. Это. . .
Nekobox - outbounds[0].transport: unknown transport type: raw
damix 01.10.2026
Фикс ошибки Правым кликом по серверу -> отладочная информация -> edit Заменить "net": "raw", на "net": "tcp", Нажать кнопку reload.
Программный домашний кинотеатр
russiannick 27.09.2026
Сподобился на программный домашний кинотеатр. В качестве ЯВУ по традиции выбрал js. В помощники взял Яндекс-Алису. Было создано три зала на разные интересы. исторические и ретро сериал Хичкок. . .
Беседа с ИИ о программистах, недопускающих к созданию и правке кода генеративные ИИ и причины этого
zorxor 21.09.2026
Раньше я радовался или получал некоторые эмоции, пусть небольшие, но всё же, от самого процесса написания кода, рекомпиляции и запуска, видя постепенное развитие программы и прочее. А теперь лень. . .
Мобильное приложение ColorStep
pavlinmavlin 17.09.2026
Реализовал приложение Красный, Зеленый, Синий в Unity3d + c#. Название изменил на ColorStep. Приложение прошло модерацию и теперь доступно для скачивания. Делал его сам, шаг за шагом — и вот,. . .
Запрет дублирования строк в табличной части
Maks 13.09.2026
Реализация из решения ниже выполнена на нетиповом справочнике "Нормы ТО" с табличной часть "Виды ТО", разработанного в КА2, со следующими реквизитами: - ВидТО (СправочникСсылка. ВидыТО); - ВидГСМ. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru