Картинка блога

При создании более или менее сложных приложений для Windows. Неизбежно возникает проблема организации доступа к данным из разных потоков. В Windows.Forms это выглядит так:
Cross-thread operation not valid: Control ‘textBox1’ accessed from a thread other than the thread it was created on.
В WPF это выглядит так:
System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it.
В обеих технологиях есть разные, но простые способы это решить.

Windows Forms.

Следующий код выкинет исключение:

namespace TestMultithreading
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            (new Action(TestThreading)).BeginInvoke(null, null); //Async. delegate call.
            textBox1.Text = "Same thread";
        }
        void TestThreading()
        {
            Thread.Sleep(100);
            textBox1.Text = "Async change"; //Exception here.
        }
    }
}

Для решения проблемы, нужно проверить, действительно ли доступ до формы пытается получить другой процесс. Создать поток в контексте формы.
Для этого, можно создать форме новый потоко-независимый метод. Мой называется AddText. Модифицированный класс выглядит следующим образом:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        (new Action(TestThreading)).BeginInvoke(null, null);
        textBox1.Text = "Same thread";
    }
    void TestThreading()
    {
        Thread.Sleep(1000);
        AddText("Async change");
    }

    public void AddText(string text)
    {
        if (this.textBox1.InvokeRequired)
        {
            Action updaterdelegate = new Action(AddText);
            try
            {
                this.Invoke(updaterdelegate, new object[] { text });
            }
            catch (ObjectDisposedException ex) { }
        }
        else
        {
            textBox1.Text = text;
        }
    }
}

После вызова AddText происходит проверка InvokeRequired, если сребуется Invoke, то он делается через делегат со значением того-же метода.

WPF

В WPF получить исключение можно тем-же образом как и в Windows.Forms:

namespace WpfApplication1
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            (new Action(TestThreading)).BeginInvoke(null, null);
        }
        void TestThreading()
        {
            textBox1.Text = "Async call"; //
        }
    }
}

Для решения задачи не требуется проверок. Дело в том, что все изменения контролируются так называемым Dispatcher-ом. Он инкапсулирован в класс
DispatcherObject, который в свою очередь является родителем DependencyObject, UIElement и соответственно всех визуальных элементов WPF. Раскрытие всех тонкостей Диспечера, задача не этой статьи. Для начала, достаточно разобраться с приоритетами (DispatcherPriority).
Вот пример решения для WPF:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        (new Action(TestThreading)).BeginInvoke(null, null);
    }
    void TestThreading()
    {
        Thread.Sleep(1000);
        Dispatcher.BeginInvoke(new Action(s =>
        {
            textBox1.Text = s;
        }), DispatcherPriority.Render, "AsyncCall");
        
    }
}

Метки:, ,

6 комментариев в “Мультипоточность в Windows.Forms и WPF”

  1. Купи учебник по русскому языку, безграмотный.

  2. Почелуй меня в задницу, учитель хренов.

  3. Привет. Благодарю за статью.. в каком-то смысле выручила.
    Но к сожалению когда виполняеться метод отданный делегату то интерфейс замирает.. а этого хотелось избежать…
    Я до этого (пока не возникало ошибок :)) использовал BackgroundWorker. Может есть возможность используя BackgroundWorker избежать появления «The calling thread cannot access…»
    Заранее брагодарен!

  4. Evgen, а так думаю, что вы вместо метода BeginInvoke(…) вызываете Invoke.

  5. Странно, но для WinForms код не заработал, ругнулся что нет метода Addtext объявленного без параметров

    Пришлось делать вот так:

    Action updaterdelegate = new Action(AddText);

  6. ёпть, не то скопировал

    ВОТ ТАК ПРИШЛОСЬ ОБЪЯВЛЯЛЯТЬ:

    Action updaterdelegate = new Action(AddText);