Синхронизация с формой из таски

Для индикации прогресса в таске существует IProgress.
А что если надо просто уведомить форму при возникновении в таске какого-то события? Тут ведь IProgress уже не поможет :thinking:

Прокинул в таску SynchronizationContext от потока формы и, вроде, заработало :man_shrugging:
Но потом решил переписать по-другому.

Уже была же такая же тема с прогрессом.
Старые добрые колбеки никто не отменял.

какие колбеки?

При старте таски передаете в нее Action какой нибудь … и пусть она вызывает его когда ей надо и передает любые сообщи.

Но ведь Action это просто делегат. Если вызвать его из таски без синхронизации - будет краш.

Для этого есть InvokeRequired , BeginInvoke
и тогда он будет вызываться в том потоке кто его создал.

Интересно теперь, как я это сделал :thinking: Всего полгода прошло, чёт всё забыл уже :man_shrugging: Пост 21 марта 2022 - у меня уже ностальгия :confusedparrot:

Так, стоп, а как? :thinking: В таску же анонимный метод запихан, типо такого:

            return await Task.Run(() =>
            {
                if (File.Exists(fnDashTmp))
                {
                    File.Delete(fnDashTmp);
                }
                IProgress<int> dashReporter = progressDash;
                Stream fileStream = File.OpenWrite(fnDashTmp);
                FileDownloader d = new FileDownloader();
                d.Connecting += (s, url) =>
                {
                    lblStatus.Text = $"Состояние: Подключение..."; //тут краш, естественно
                    lblProgress.Text = null;
                    lblStatus.Refresh();
                };
                ........

Никто же не мешает сделать его функцией.

не понял :man_shrugging:


            await Task.Run(() =>
            {
                for (int i = 0; i < 9999; ++i)
                {
                    Action action = () =>
                    {
                        lblProgress.Text = i.ToString();
                        lblProgress.Refresh();
                        Thread.Sleep(3000);
                    };
                    Invoke(action);
                }
            });

вот так чтоли? :thinking: Вроде работает

Тогда не понятно, зачем нужен IProgress, если через Action удобнее?

Чем удобнее?
А так IProgress универсальнее. Invoke это ж метод винформс контрола/формы. Его надо как-то передать коду потока, который может находиться совсем не в коде формы, а, например, в библиотеке. И библиотека может быть рассчитана не только на винформс.

А IProgress он не только из кода формы? Откуда он знает, что надо именно с потоком формы синхронизироваться?

Progress должен создаваться в основном потоке. Он использует SynchronizationContext.Current.

А, понял. Синхронизируется с тем потоком, в котором был создан.

Тем, что можно написать вот так:

                    Action action = () =>
                    {
                         //делаем, что хотим
                    };
                    Invoke(action);

И не надо писать целый класс, чтобы, например, изменить текст в одном контроле. А если, например, что-то ещё надо сделать (кроме изменения текста), как это передать в IProgress? Придётся же кучу флагов вводить, что и когда сделать. Или отдельный IProgress и класс на каждое действие. Это же не удобно.

А можно вообще так:

                    Invoke((MethodInvoker)delegate
                    {
                            //
                    });

Так ещё короче.

Разве нельзя просто Invoke(() => ...)?

По-разному можно, на что хватит фантазии. Хоть просто колбэк передавать в этот класс при создании.

Точнее стандартный Progress<T> и есть же такой. https://learn.microsoft.com/en-us/dotnet/api/system.progress-1.-ctor?view=net-7.0#system-progress-1-ctor(system-action((-0)))

Так что даже свой класс не нужен.

Неа, нельзя.

У меня не хватает :man_shrugging:

А если таска создана не в классе формы

namespace WindowsFormsApp2
{
    internal class MySubClass
    {
        public bool Active { get; private set; }

        public void Start()
        {
            if (!Active)
            {
                Active = true;
                Task.Run(() =>
                {
                    int i = 0;
                    while (Active)
                    {
                        Thread.Sleep(1000);
                        SomethingHappened(i++);
                    }
                });
            }
        }

        protected virtual void SomethingHappened(int i)
        {
            System.Diagnostics.Debug.WriteLine($"Something is happened {i} times in subclass");
        }
    }
}
namespace WindowsFormsApp2
{
    internal class MyClass : MySubClass
    {
        protected override void SomethingHappened(int i)
        {
            System.Diagnostics.Debug.WriteLine($"Something is happened {i} times in class");
            //тут надо синхронизироваться и какой-нибудь делегат должен быть
        }
    }
}
namespace WindowsFormsApp2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            MyClass myClass = new MyClass();
            myClass.Start();
        }


       //Тут где-нибудь ещё Stop() должен быть
    }
}

Не совсем понимаю, как сюда Action прикрутить :thinking: Обычно я в таких ситуациях прокидываю SynchronizationContext.Current от потока с формой и всё работает. Ещё на гитхабе видел, там в конструктор тупо передаёт саму форму и от неё вызывают Invoke(). Не знаю, насколько эти способы корявые.
Но что если

?