MVP pattern и async
От: Lev_Limin  
Дата: 28.06.17 13:30
Оценка:
Жизнь заставила изучать программирование. Написал небольшую программу, которая по списку кадастровых номеров, выгребает открытые данные с сайта росреестра.
Программа WinForm, в ней массив номеров делится по количеству потоков и после, в этих потоках выгребается.

Но тут попросили программу подкрутить, накидали список переделок. И я решил, что по ходу дела надо освоить что-то новое для себя.
Новым оказался паттерн MVP. Затык оказался, как к MVP прикрутить многопоточность и асинхронность.

Что бы проще было разобраться, накидал простенькое тестовое приложение.

  Код тестового приложения
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            Form1 frm = new Form1();
            Model model = new Model();
            Presenter presenter = new Presenter(frm, model);

            Application.Run(frm);
        }
...
    public partial class Form1 : Form
    {
    // Тут файл с номерами
        private string path = @"600.txt";
        public Func<string, Task> start { get; set; }
        private SynchronizationContext sctx;
        private TaskScheduler ts;

        public Form1()
        {
            InitializeComponent();
            sctx = SynchronizationContext.Current;
            ts = TaskScheduler.FromCurrentSynchronizationContext();
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            await start(path).ConfigureAwait(true);

            label1.Text = "Ok";
        }

        public void SetLabel(string val)
        {
            label1.Text = val;
        }

        public void SetProgress(int mx)
        {
            progressBar1.Minimum = 0;
            progressBar1.Maximum = mx;
            progressBar1.Step = 1;
        }

        public void StepProgress()
        {
            sctx.Send(x => { progressBar1.PerformStep(); SetLabel(progressBar1.Value.ToString()); }, null);
            //progressBar1.PerformStep();
            //SetLabel(progressBar1.Value.ToString());
        }

        public TaskScheduler GetScheaduler()
        {
            return ts;
        }
    }
...
    public class Presenter
    {
        private Form1 view;
        private Model model;

        public Presenter(Form1 frm, Model mdl)
        {
            view = frm;
            model = mdl;
            model.presenter = this;
            view.start += strt;
        }

        private Task strt(string arg)
        {
            string[] lines = File.ReadAllLines(arg);
            view.SetProgress(lines.Length * 10);

            Task[] tsks = new Task[10];

            for (int i = 0; i < 10; i++)
            {
                tsks[i] = Task.Factory.StartNew(() => model.ProcessMethodAsync(lines), CancellationToken.None);
            }

            return Task.WhenAll(tsks);
        }

        public void StepProgress()
        {
            view.StepProgress();
        }
...
    public class Model
    {
        public Presenter presenter;

        public void ProcessMethodAsync(string[] lines)
        {
            foreach (var line in lines)
            {
                Task.Delay(1).Wait();
                presenter.StepProgress();
            }
        }
    }


И теперь вопросы
1. Каким образом, в подобных приложениях — MVP, MVC и т.д. асинхронно обновлять UI?
Ибо тащить обновление UI через несколько слоёв как-то накладно.
На стаковерфлоу мне насоветовали паттерн Observer, мол пусть он занимается маршрутизацией сообщений.

2. Почему, в методе Form1.button1_Click надо обязательно писать .ConfigureAwait(true), что бы в label1 по окончании было слово "Ok"?
Разве .ConfigureAwait(true) не подразумевается по умолчанию? Или же поведение будет совершенно другое, если .ConfigureAwait не писать?

3. Почему, если вместо sctx.Send, написать sctx.Post, то программа выпадает в осадок по эксепшену DisconnectedСontext?

4. Если в Model.ProcessMethodAsync закаментить Task.Delay(1).Wait() то приложение начинает работать словно оно синхронное и UI
перестаёт обновляться и даже форму подвигать нельзя. Связано ли это с тем, что у меня полезной нагрузки в методе нет, и вся работа, это
обращение к UI и винда тупо не успевает реагировать?

Заранее спасибо!
Отредактировано 28.06.2017 13:32 Lev_Limin . Предыдущая версия .
Re: MVP pattern и async
От: LWhisper  
Дата: 28.06.17 14:53
Оценка: 2 (1)
Здравствуйте, Lev_Limin, Вы писали:

L_L>И я решил, что по ходу дела надо освоить что-то новое для себя.

L_L>Новым оказался паттерн MVP. Затык оказался, как к MVP прикрутить многопоточность и асинхронность.
Рекомендую заодно освоить WPF. Для него сейчас есть большая база реазличных вариантов реализации паттернов, в том числе MVC и MVVM, главное помни, что любая попытка реализовать несовершенный паттерн (а они все такие) повсеместно ни к чему хорошему не приведёт. Нужно искать золотую середину.

L_L>И теперь вопросы

L_L>1. Каким образом, в подобных приложениях — MVP, MVC и т.д. асинхронно обновлять UI?
L_L>Ибо тащить обновление UI через несколько слоёв как-то накладно.
L_L>На стаковерфлоу мне насоветовали паттерн Observer, мол пусть он занимается маршрутизацией сообщений.
Именно что асинхронно. Один поток наполняет источник данных, другой эти данные использует, независимо друг от друга. Поток наполняющий источник может сингализировать о том, что он изменился. Например, посредством событий. В WPF за это отвечают реализации интерфейсов IObservableCollection и INotifyPropertyChanged.
Если тащить что-то через несколько слоёв накладно, можешь сохранить значение в AsyncLocal, только перед этим освой паттерны с его использованием, чтобы код остался читабельным и понятным, предсказуемым в каждый момент времени.

L_L>2. Почему, в методе Form1.button1_Click надо обязательно писать .ConfigureAwait(true), что бы в label1 по окончании было слово "Ok"?

L_L>Разве .ConfigureAwait(true) не подразумевается по умолчанию? Или же поведение будет совершенно другое, если .ConfigureAwait не писать?
Не обязательно. Подразумевается. Но учитывая то, что во всех прочих случаях люди вынуждены писать .ConfigureAwait(false), почему бы не делать это повсеместно? Кроме того существует призрачный шанс, что разработчики пойдут на поводу у общественности и добавят возможность изменить значение по-умолчанию, например при помощи конфигурационного файла или через какое-нибудь статичное свойство.

L_L>4. Если в Model.ProcessMethodAsync закаментить Task.Delay(1).Wait() то приложение начинает работать словно оно синхронное и UI

L_L>перестаёт обновляться и даже форму подвигать нельзя. Связано ли это с тем, что у меня полезной нагрузки в методе нет, и вся работа, это
L_L>обращение к UI и винда тупо не успевает реагировать?
Ты забиваешь очередь сообщений окна обновлениями контрола. Поток обработки сообщений один. Если он забит, естественно, ты не можешь выполнять с формой других действий. Асинхронные методы не спасают тебя от таких ситуаций. Ты должен самостоятельно контролировать обновление данных, обновляя контролы не чаще, чем UI способен отобразить без задержки (обычно выбирают интервалы времени от 100 миллисекунд для отображения быстро сменяющейся информации до 5 секунд для продолжительных операций, если есть таймер, то логично выбрать интервал времени, соответствующий его точности, например, 1 секунду).
Re: MVP pattern и async
От: TK Лес кывт.рф
Дата: 28.06.17 20:46
Оценка: -1
Здравствуйте, Lev_Limin, Вы писали:

L_L>Новым оказался паттерн MVP. Затык оказался, как к MVP прикрутить многопоточность и асинхронность.

L_L>Что бы проще было разобраться, накидал простенькое тестовое приложение.

L_L>И теперь вопросы

L_L>1. Каким образом, в подобных приложениях — MVP, MVC и т.д. асинхронно обновлять UI?

Выкиньте всякую ерунду с шедулерами, Task.Factory и ComfigureAwait. UI обновляет следующим кодом:

async void Update()
{
  var data = await GetData();
  UpdateUI(data);
}
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[2]: MVP pattern и async
От: VladCore  
Дата: 29.06.17 11:49
Оценка:
Здравствуйте, TK, Вы писали:


TK>Выкиньте всякую ерунду с шедулерами, Task.Factory и ComfigureAwait. UI обновляет следующим кодом:


TK>
TK>async void Update()
TK>{
TK>  var data = await GetData();
TK>  UpdateUI(data);
TK>}
TK>


ты невнимательно вопрос прочитал. надо из множества потоков по мере обработки данных обновлять UI, а не ждать пока все все все данные обработаются в твоем GetData
Re[2]: MVP pattern и async
От: Философ Ад http://vk.com/id10256428
Дата: 29.06.17 16:34
Оценка: 4 (1)
Здравствуйте, TK, Вы писали:

TK>Выкиньте всякую ерунду с шедулерами, Task.Factory и ComfigureAwait. UI обновляет следующим кодом:


TK>
TK>async void Update()
TK>


async — слишком сложное слово, которое за собой много чего тянет, и в конечном счёте всё равно заставляет разобраться в происходящем за кадром.
Всё сказанное выше — личное мнение, если не указано обратное.
Re: MVP pattern и async
От: Философ Ад http://vk.com/id10256428
Дата: 29.06.17 16:46
Оценка: 2 (1)
Здравствуйте, Lev_Limin, Вы писали:
  оригинальный код
L_L> public partial class Form1 : Form
L_L> {
L_L> // Тут файл с номерами
L_L> private string path = @"600.txt";
L_L> public Func<string, Task> start { get; set; }
L_L> private SynchronizationContext sctx;
L_L> private TaskScheduler ts;

L_L> public Form1()

L_L> {
L_L> InitializeComponent();
L_L> sctx = SynchronizationContext.Current;
L_L> ts = TaskScheduler.FromCurrentSynchronizationContext();
L_L> }

L_L> private async void button1_Click(object sender, EventArgs e)

L_L> {
L_L> await start(path).ConfigureAwait(true);

L_L> label1.Text = "Ok";

L_L> }

Это можно переписать примерно вот так:
        private void button1_Click(object sender, EventArgs e)
        {
            var task = Task.Factory.StartNew(
                () => start(path)
                , CancellationToken.None
                , TaskCreationOptions.None
                , TaskScheduler.Default
            );

            task.ContinueWith(p_task =>
             {
                label1.Text = "Ok";
             }, CancellationToken.None
             , TaskContinuationOptions.None
             , ts);
        }


После чего переименовать ts в uiTaskScheduler — тогда всё встанет на свои места, и всё будет понятно. Не связывайся с async/await до тех пор пока не поймёшь, что здесь написано и зачем (до последней буквы).

UPD:
На твой 4-й вопрос я отвечать не буду. Вместо этого скажу, что тебе нужно разобраться, что TaskScheduler.Default, чем он отличается от TaskScheduler.Current и зачем нужно указывать TaskScheduler в параметре создания таски.
Всё сказанное выше — личное мнение, если не указано обратное.
Отредактировано 29.06.2017 16:51 Философ . Предыдущая версия .
Re: MVP pattern и async
От: _Raz_  
Дата: 30.06.17 08:41
Оценка: 2 (1)
Здравствуйте, Lev_Limin, Вы писали:

L_L>1. Каким образом, в подобных приложениях — MVP, MVC и т.д. асинхронно обновлять UI?


    public partial class Form1 : Form
    {
        public void ReportProgress()
        {
            var reportFunc = new Action(() =>
            {
                progressBar1.PerformStep();
                label1.Text = progressBar1.Value.ToString();
            });

            if (InvokeRequired)
                BeginInvoke(reportFunc);
            else
                reportFunc();
        }

И не надо никаких танцев с контекстами и шедулерами.

L_L>Ибо тащить обновление UI через несколько слоёв как-то накладно.


А обновлением UI занимается один слой — представление. Он получает событие прогресса от модели и вызывает метод вью для обновления UI.

Ну и, судя по коду, ты плохо представляешь себе ответсвенность уровней. У тебя и вью и модель знают о представлении, а должно быть наоборот — Presenter должен знать о них обоих, а они о нем ничего.

L_L>На стаковерфлоу мне насоветовали паттерн Observer, мол пусть он занимается маршрутизацией сообщений.


Совсем мимо. Тебе же прогресс выполнения получить, а не события с новыми данными.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re[3]: MVP pattern и async
От: TK Лес кывт.рф
Дата: 30.06.17 09:13
Оценка:
Здравствуйте, Философ, Вы писали:

Ф>async — слишком сложное слово, которое за собой много чего тянет, и в конечном счёте всё равно заставляет разобраться в происходящем за кадром.


так про то и речь — пока с одним словом не разобрался, спешить с использованием других не стоит.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[3]: MVP pattern и async
От: TK Лес кывт.рф
Дата: 30.06.17 09:25
Оценка: 2 (1)
Здравствуйте, VladCore, Вы писали:

VC>ты невнимательно вопрос прочитал. надо из множества потоков по мере обработки данных обновлять UI, а не ждать пока все все все данные обработаются в твоем GetData


какая тут разница сколько потоков?
Если источник данных предоставляет pull интерфейс то, используйте UpdateUI(await GetData()).
Если это push интерфейс то надо использовать IObservable и аналоги — никаких await там не надо.

Про потоки стоит думать в контексте потоков данных. А конкретно threads и все что с ними связано от ui надо абстрагировать.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[2]: MVP pattern и async
От: Lev_Limin  
Дата: 30.06.17 12:32
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Здравствуйте, Lev_Limin, Вы писали:


L_L>>И я решил, что по ходу дела надо освоить что-то новое для себя.

L_L>>Новым оказался паттерн MVP. Затык оказался, как к MVP прикрутить многопоточность и асинхронность.
LW>Рекомендую заодно освоить WPF. Для него сейчас есть большая база различных вариантов реализации паттернов, в том числе MVC и MVVM, главное помни, что любая попытка реализовать несовершенный паттерн (а они все такие) повсеместно ни к чему хорошему не приведёт. Нужно искать золотую середину.

Спасибо. Я понимаю, что идеала нет, но из-за отсутствия опыта, постоянно терзает мысль, что сделать можно лучше и "правильнее".

L_L>>2. Почему, в методе Form1.button1_Click надо обязательно писать .ConfigureAwait(true), что бы в label1 по окончании было слово "Ok"?

L_L>>Разве .ConfigureAwait(true) не подразумевается по умолчанию? Или же поведение будет совершенно другое, если .ConfigureAwait не писать?
LW>Не обязательно. Подразумевается. Но учитывая то, что во всех прочих случаях люди вынуждены писать .ConfigureAwait(false), почему бы не делать это повсеместно? Кроме того существует призрачный шанс, что разработчики пойдут на поводу у общественности и добавят возможность изменить значение по-умолчанию, например при помощи конфигурационного файла или через какое-нибудь статичное свойство.

Похоже на то, что StartNew не восстанавливает контекст и делать это необходимо явно.
Re: MVP pattern и async
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 30.06.17 12:40
Оценка: 2 (1)
Здравствуйте, Lev_Limin, Вы писали:
Можешь использовать асинхронные очереди
https://msdn.microsoft.com/ru-ru/library/hh873173(v=vs.110).aspx

Смотри AsyncProducerConsumerCollection
В C# 7 есть ValueTask https://www.infoq.com/articles/Patterns-Practices-CSharp-7
и солнце б утром не вставало, когда бы не было меня
Отредактировано 02.07.2017 14:03 Serginio1 . Предыдущая версия . Еще …
Отредактировано 02.07.2017 14:02 Serginio1 . Предыдущая версия .
Re[2]: MVP pattern и async
От: Lev_Limin  
Дата: 30.06.17 12:42
Оценка:
Здравствуйте, Философ, Вы писали:

>После чего переименовать ts в uiTaskScheduler — тогда всё встанет на свои места, и всё будет понятно. Не связывайся с async/await до тех пор пока не поймёшь, что здесь написано и зачем (до последней буквы).


Спасибо. Такой вариант я тоже пробовал. Я почему использовал context.Send(...), в интернете много пишут, дескать нынче это самый правильный подход.
Я так понимаю, что внутри он всё равно работает через Invoke?

Ну и по поводу async/await — я как раз изучаю их, что бы было понятно. Ну и снова в интернете пишут, что лучше использовать async/await, а не Task/ContinueWith, мол оно интуитивный.

>UPD:

>На твой 4-й вопрос я отвечать не буду. Вместо этого скажу, что тебе нужно разобраться, что TaskScheduler.Default, чем он отличается от TaskScheduler.Current и зачем нужно указывать TaskScheduler в параметре создания таски.

Тут я и сам разобрался уже.

И про последний вопрос (Send vs Post) — походу в эксепшен падает только отладчик в vs2017. Если запускать код вне студии, то всё работает нормально.
Re[4]: MVP pattern и async
От: Lev_Limin  
Дата: 30.06.17 12:43
Оценка:
Здравствуйте, TK, Вы писали:

TK>Здравствуйте, VladCore, Вы писали:


VC>>ты невнимательно вопрос прочитал. надо из множества потоков по мере обработки данных обновлять UI, а не ждать пока все все все данные обработаются в твоем GetData


TK> какая тут разница сколько потоков?

TK>Если источник данных предоставляет pull интерфейс то, используйте UpdateUI(await GetData()).
TK>Если это push интерфейс то надо использовать IObservable и аналоги — никаких await там не надо.

TK>Про потоки стоит думать в контексте потоков данных. А конкретно threads и все что с ними связано от ui надо абстрагировать.


Большое спасибо за этот пост. Вопрос и был в том, как правильнее делать подобные вещи, обновлять UI если есть несколько слоёв приложения.
Про недопонимание MVP могу ответить, что да, в теории вроде всё понимаю, а на практике пока трудности с правильным разделением обязанностей.
Re[4]: MVP pattern и async
От: Философ Ад http://vk.com/id10256428
Дата: 30.06.17 18:43
Оценка:
Здравствуйте, TK, Вы писали:

TK>Здравствуйте, Философ, Вы писали:


Ф>>async — слишком сложное слово, которое за собой много чего тянет, и в конечном счёте всё равно заставляет разобраться в происходящем за кадром.


TK>так про то и речь — пока с одним словом не разобрался, спешить с использованием других не стоит.


Тут дело в том, что ты советуешь async, когда нет чёткого понимания как будет работать Task.Factory.StartNew(). При этом таких людей большинство — у них есть лишь иллюзия понимания происходящего в коде: оно сложно и неочевидно. Это сложно и неочевидно даже для людей давно и плотно работающих с многопоточностью, и именно поэтом большинство не может ответить на вопрос, что за магия происходит в такой строке:
...
Task.Delay(1).Wait();


Сопровождать потом такой код невозможно. Даже ты вряд ли сможешь сказать что оно будет делать, и как именно работать: для этого тебе придётся перелопатить почти весь код, в поисках всяких ConfigureAwait() и инициализаций TaskScheduler'ов.
Собственно поэтому я категорически против использования этой маги в продакшене, и новичкам бы точно не стал советовать выбросить все эти таски и переписать всё на async'и. Да блин, хотя-бы потому, что один "лишний" await приведёт к тому, что ты потом будешь в дампе ковыряться, который для тебя снял пользователь с "зависшей" софтины.
Всё сказанное выше — личное мнение, если не указано обратное.
Re[4]: MVP pattern и async
От: VladCore  
Дата: 02.07.17 13:36
Оценка:
Здравствуйте, TK, Вы писали:

VC>>ты невнимательно вопрос прочитал. надо из множества потоков по мере обработки данных обновлять UI, а не ждать пока все все все данные обработаются в твоем GetData


TK> какая тут разница сколько потоков?

TK>Если источник данных предоставляет pull интерфейс то, используйте UpdateUI(await GetData()).
TK>Если это push интерфейс то надо использовать IObservable и аналоги — никаких await там не надо.

прочитай вопрос хотя бы раз. подсказываю:

массив номеров делится по количеству потоков и после, в этих потоках выгребается.


TK>Про потоки стоит думать в контексте потоков данных.


я не спрашивал как следует думать о потоках

TK>А конкретно threads и все что с ними связано от ui надо абстрагировать.


я не писал что ненадо абстрагироваться от потоков данных
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.