При создании более или менее сложных приложений для 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");
}
}
Метки:C#, разработка, Windows
Похожие статьи
- 30 августа 2012 -- Reactive extensions простая очередь без Contrib пакета (0)
- 6 февраля 2009 -- Загрузка Flash в Windows.Forms и WPF. (2)
- 8 марта 2011 -- C#: Silverlight таймаут (Timeout) (2)
- 22 августа 2008 -- Собственная страница для обработки ошибок на ASP.NET (0)
- 15 сентября 2011 -- Еще раз о работе со службами (Windows Service) на C# (0)
29 ноября, 2011 at 10:21
Купи учебник по русскому языку, безграмотный.
14 декабря, 2011 at 9:19
Почелуй меня в задницу, учитель хренов.
25 января, 2012 at 19:09
Привет. Благодарю за статью.. в каком-то смысле выручила.
Но к сожалению когда виполняеться метод отданный делегату то интерфейс замирает.. а этого хотелось избежать…
Я до этого (пока не возникало ошибок :)) использовал BackgroundWorker. Может есть возможность используя BackgroundWorker избежать появления «The calling thread cannot access…»
Заранее брагодарен!
24 ноября, 2012 at 16:57
Evgen, а так думаю, что вы вместо метода BeginInvoke(…) вызываете Invoke.
19 июля, 2014 at 2:31
Странно, но для WinForms код не заработал, ругнулся что нет метода Addtext объявленного без параметров
Пришлось делать вот так:
Action updaterdelegate = new Action(AddText);
19 июля, 2014 at 2:33
ёпть, не то скопировал
ВОТ ТАК ПРИШЛОСЬ ОБЪЯВЛЯЛЯТЬ:
Action updaterdelegate = new Action(AddText);