2015-08-07 4 views
6

Я только что ответил на вопрос, может ли Task обновить интерфейс. Когда я играл с моим кодом, я понял, что не совсем понимаю, о чем я говорю.Фоновая задача иногда может обновлять пользовательский интерфейс?

Если у меня есть окна формы с одним контролем txtHello на него, я могу обновить пользовательский интерфейс от задачи, кажется, если бы я сразу же сделать это на Task.Run:

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
     Task.Run(() => 
     { 
      txtHello.Text = "Hello"; 
     }); 
    } 
} 

Однако если я Thread.Sleep даже для 5 миллисекунд, генерируется ожидаемый CrossThread ошибка:

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
     Task.Run(() => 
     { 
      Thread.Sleep(5); 
      txtHello.Text = "Hello"; //kaboom 
     }); 
    } 
} 

Я не уверен, почему это происходит. Есть ли какая-то оптимизация для чрезвычайно короткой работы Task?

+0

Это станет для меня неожиданностью, если это правда! –

ответ

6

Вы не отправляли стек исключений трассировки, но я ожидаю, что это выглядело примерно так:

System.InvalidOperationException: Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on. 
    at System.Windows.Forms.Control.get_Handle() 
    at System.Windows.Forms.Control.set_WindowText(String value) 
    at System.Windows.Forms.TextBoxBase.set_WindowText(String value) 
    at System.Windows.Forms.Control.set_Text(String value) 
    at System.Windows.Forms.TextBoxBase.set_Text(String value) 
    at System.Windows.Forms.TextBox.set_Text(String value) 
    at WindowsFormsApplicationcSharp2015.Form1.<.ctor>b__0_0() in D:\test\WindowsFormsApplicationcSharp2015\Form1.cs:line 27 

Мы можем видеть, что исключение из свойства Control.Handle геттера. И в самом деле, если мы посмотрим на source code для этого свойства, там, как и ожидалось:

public IntPtr Handle { 
    get { 
     if (checkForIllegalCrossThreadCalls && 
      !inCrossThreadSafeCall && 
      InvokeRequired) { 
      throw new InvalidOperationException(SR.GetString(SR.IllegalCrossThreadCall, 
                  Name)); 
     } 

     if (!IsHandleCreated) 
     { 
      CreateHandle(); 
     } 

     return HandleInternal; 
    } 
} 

Самое интересное, когда мы смотрим на код, который вызывает Control.Handle. В этом случае, это свойство Control.WindowText сеттера:

set { 
    if (value == null) value = ""; 
    if (!WindowText.Equals(value)) { 
     if (IsHandleCreated) { 
      UnsafeNativeMethods.SetWindowText(new HandleRef(window, Handle), value); 
     } 
     else { 
      if (value.Length == 0) { 
       text = null; 
      } 
      else { 
       text = value; 
      } 
     } 
    } 
} 

Обратите внимание, что Handle свойства вызывается только если IsHandleCreated является true.

И для полноты картины, если мы посмотрим на код для IsHandleCreated мы видим следующее:

public bool IsHandleCreated { 
    get { return window.Handle != IntPtr.Zero; } 
} 

Таким образом, по этой причине вы не получите исключение, потому, что к тому времени Task исполняет, то оконный дескриптор еще не создан, что следует ожидать, так как Task начинается в конструкторе формы, то есть до отображения формы.

Прежде чем дескриптор окна будет создан, изменение свойства еще не требует какой-либо работы из потока пользовательского интерфейса. Поэтому во время этого небольшого временного окна в начале вашей программы, казалось бы, можно вызвать методы из экземпляров управления из потока, отличного от UI, без исключения «кросс-потока». Но, очевидно, существование этого особого маленького временного окна не меняет того факта, что мы всегда должны обязательно использовать методы управления из потока пользовательского интерфейса для обеспечения безопасности.

Чтобы доказать, что время создания дескриптора окна является определяющим фактором при получении (или нет) исключения «кросс-потока», попробуйте изменить свой пример, чтобы принудительно создать дескриптор окна перед тем, как запустить задачу и обратите внимание, как вы теперь будете последовательно получить ожидаемое исключение, даже без сна:

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 

     // Force creation of window handle 
     var dummy = txtHello.Handle; 

     Task.Run(() => 
     { 
      txtHello.Text = "Hello"; // kaboom 
     }); 
    } 
} 

Соответствующая документация: Control.Handle

Если ручка еще не была создана, ссылки это свойство будет принудительно который будет создан.

Смежные вопросы