Автор: Sergey Teplyakov

Человек даже немного поработав с Windows Forms наверняка сталкивался с замечательным исключением System.InvalidOperationException с таким описанием: {'Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.'} Этим сообщением среда выполнения недвусмысленно дает понять, что обращаться к элементам управления пользовательского интерфейса можно только из того потока, который создал этот элемент управления. Такое поведение обусловлено деталями реализации, в частности использованием однопоточной модели аппартаментов (Single-threaded Apartment, STA) и механизма обработки оконных сообщений. Вот простенький пример кода, который приводит к генерации такого исключения:

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent(),

// TextBox Button.

// TextBox ,

// Button

timer = new Timer(AsyncHandler),

}

private void button1_Click(object sender, EventArgs e)

{

timer.Change(1000, 1000),

}

private void AsyncHandler(object data)

{

tickCount++,

textBox1.Text = tickCount.ToString(),

}

private readonly System.Threading.Timer timer,

private int tickCount,

}

Существует вполне простой и понятный способ решения этой проблемы, путем проверки свойства элемента управления InvokeRequired с последующим вызовом метода Invoke.
Вот самый простой и примитивный способ:

private void AsyncHandler(object data)

{

tickCount++,

Action<,int>, action = DoChangeTicks,

if (InvokeRequired)

{

Invoke(action, tickCount),

}

else

{

action(tickCount),

}

}

private void DoChangeTicks(int count)

{

textBox1.Text = tickCount.ToString(),

}

Способ простой, но не совсем удобный. В данном случае вспомогательный метод только увеличивает связность и усложняет понимает решаемой задачи. Если воспользоваться новшествами C#3.0 в виде лямбда-выражений, можно сделать несколько более удобную реализацию следующего вида:

private void AsyncHandler(object data)

{

tickCount++,

Action action = () =>, textBox1.Text = tickCount.ToString(),

if (InvokeRequired)

{

Invoke(action),

}

else

{

action(),

}

}

Уже лучше. Мы избавились от необязательного метода, который неразрывно связан с тем действием, которое выполняет метод AsyncHandler. Но, все же, еще есть над чем подумать.
Следующий вариант решения был честно подсмотрен в неплохой книжке: Bill Wagner 'More Effective C#'.

/// <,summary>,

/// .

/// <,/summary>,

public static class ControlExtentions

{

/// <,summary>,

/// control.Invoke, .

/// <,/summary>,

/// <,param name=control>, <,/param>,

/// <,param name=doit>, <,/param>,

public static void InvokeIfNeeded(this Control control, Action doit)

{

if (control.InvokeRequired)

control.Invoke(doit),

else

doit(),

}

/// <,summary>,

/// control.Invoke, .

/// <,/summary>,

/// <,typeparam name=T>, <,/typeparam>,

/// <,param name=control>, <,/param>,

/// <,param name=doit>, <,/param>,

/// <,param name=arg>, <,/param>,

public static void InvokeIfNeeded<,T>,(this Control control, Action<,T>, doit, T arg)

{

if (control.InvokeRequired)

control.Invoke(doit, arg),

else

doit(arg),

}

}

С помощью этого вспомогательного класса, реализация метода, взаимодействующего с элементами управления из других потоков, будет следующей:

private void AsyncHandler(object data)

{

tickCount++,

this.InvokeIfNeeded(

() =>, textBox1.Text = tickCount.ToString()

),

}

Помогла статья? Оцените её!
0 из 5. Общее количество голосов - 0
 

You have no rights to post comments

Дмитрий Крикунов

Публикую статьи, обучающие курсы и новости по программированию: алгоритмам, языкам (С++, Java), параллельному программированию, паттернам и библиотекам (Qt, boost).