2014-01-23 9 views
3

У меня есть способ экспорта данных. Я делаю это через новый поток, так что графический интерфейс остается отзывчивым. В конце он открывает SaveFileDialog, который не работает без вызова. При изменении ниже он работает, но опять же, GUI не отвечает. Любая подсказка?Thread and GUI with invoke issue

private void button1_Click(object sender, EventArgs e) 
{ 
    Thread thread = new Thread(method); 
    thread.Start(); 
} 
public void medhod() 
{ 
    if (this.InvokeRequired) 
    { 
     Invoke(new MethodInvoker(delegate() { method(); })); 
    } 
    else 
    { 
     //Code 
     //SaveFileDialog 
    } 
} 

* Edit: Другой подход должен был бы оставить код экспорта в новом потоке, и поставить SaveFileDialog обратно к исходной теме. Все, что мне нужно, это 1-й поток, чтобы «приостановить», а затем продолжить, как только закончится второй поток. Идеи приветствуются.

+0

Не-пользовательские потоки и модальные диалоги не работают хорошо ... – James

+0

Использование потоков напрямую для большинства задач фактически устарело. Предлагаемой технологией является TPL (http://msdn.microsoft.com/en-us/library/dd537609%28v=vs.110%29.aspx) с точки зрения использования средства «Задача». Он не решает вашу проблему, хотя, как @James сказал – Samuel

+0

* Я делаю это через новый поток, так что графический интерфейс остается отзывчивым * Я ничего не вижу! вы просто делаете все в потоке пользовательского интерфейса. –

ответ

3

Ваша проблема, вероятно, в том, что точки зрения Luaan. У вас длинная операция, которую вы хотите поместить в поток, но затем вы вызываете всю операцию в поток пользовательского интерфейса, и она будет блокировать поток пользовательского интерфейса на время.

Делают это так:

private void button1_Click(object sender, EventArgs e) 
{ 
    (new Thread(method)).Start(); 
} 
private void method() 
{ 
    //Code 
    Invoke(() => 
    { 
     //SaveFileDialog 
    }); 
} 

Вам не нужно проверять InvokeRequired, потому что это будет необходимо в любом случае. Способ, которым вы его используете, - это шаблон метода определения, который можно вызвать из любого потока. Но в этом случае он обычно содержит очень короткую операцию для взаимодействия с элементами управления пользовательского интерфейса.

+0

"* вам не нужно проверять' InvokeRequired', потому что это будет необходимо в любом случае * "- не если' метод' также вызывается из пользовательского интерфейса напрямую. – James

+1

@ Джеймс, я согласен, но очевидно, что OP скрывает длительную операцию, которую он не хочет запускать в потоке пользовательского интерфейса. – Sinatr

+0

Один маленький выпуск. Если я делаю это, это нормально, но он дважды запускает часть «Код», прежде чем открывать «SaveFileDialog». Поэтому, когда вы достигаете конца «Кода», он снова запускает его. Есть идеи по этому поводу? – fishmong3r

-1

попробуйте использовать BeginInvoke(), который является асинхронным, а не Invoke, который является синхронным , см. What's the difference between Invoke() and BeginInvoke() для лучшей трактовки аргумента.

+0

Если я просто изменяю 'Invoke (новый MethodInvoker (delegate() {ws_uptime_query();}));' to' BeginInvoke (новый MethodInvoker (delegate() {ws_uptime_query();})); 'он все еще не реагирует. Можете ли вы поделиться некоторым примером кода? – fishmong3r

+1

'BeginInvoke' ничего не изменит. – James

-1

Попробуйте это:

void btnClick(object sender, EventArgs e) 
{ 
    var t = new Thread(doStuff); 
    t.SetApartmentState(ApartmentState.STA); 
    t.Start(); 
} 

void doStuff() 
{ 
    SaveFileDialog sfd = new SaveFileDialog(); 

    sfd.ShowDialog(); 
} 

SaveFileDialog запускается в отдельном потоке (он должен иметь однопоточную квартиру), и она не блокирует поток пользовательского интерфейса приложения. Однако будьте очень осторожны во всем, что вы делаете, это может быть очень нестабильно.

Основная проблема заключается в том, что в вашем приложении работает цикл обмена сообщениями Windows, который представляет собой цикл, который выполняет итерацию по сообщениям Windows, поступающим из ОС (и других приложений). Если по какой-то причине петля застряла, приложение перестает отвечать на запросы (когда вы нажимаете на мышь, ОС отправляет WM_MOUSEDOWN и еще много методов в очередь сообщений, которые должны быть депопулированы контуром сообщений, чтобы что-либо сделать). Быть в методе ShowDialog - это один из способов заклинивания цикла - ваша форма больше не обрабатывает никаких сообщений, потому что у нее никогда не будет шанса.

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

Теперь, как диалоговое окно получает сообщения Windows? На практике он создает собственный WM-контур. Это очень маленький, но еще один цикл while, который заканчивается только при закрытии модального диалога - блокирование цикла обмена сообщениями родительской формы (или, скорее, приложения).

Проблема с приведенным выше кодом заключается в том, что он может украсть цикл обмена сообщениями из нашего родительского окна. Решение состоит в том, чтобы явно создать новое окно с новым контуром обмена сообщениями, явным образом передавая владельцу ShowDialog:

void doStuff() 
{ 
    NativeWindow nw = null; 

    try 
    { 
     nw = new NativeWindow(); 

     nw.CreateHandle(new CreateParams()); 
     SaveFileDialog sfd = new SaveFileDialog(); 

     sfd.ShowDialog(nw); 
    } 
    finally 
    { 
     if (nw != null) 
      nw.DestroyHandle(); 
    } 
} 
+0

Вы не должны запускать компоненты пользовательского интерфейса в потоке, отличном от UI. «* Будьте очень осторожны во всем, что вы делаете, это может быть очень нестабильно *» - почему бы даже предложить это? – James

+0

@James Потому что вам нужно только тщательно обрабатывать взаимодействие между двумя темами пользовательского интерфейса - вот где, наконец, начинает действовать метод 'Invoke'. – Luaan

+0

Думаете, вы пропустили точку моего комментария, это ** никогда не рекомендуется пытаться модифицировать * любой * компонент пользовательского интерфейса в любом другом потоке, чем тот, на котором он был создан, потому что все компоненты пользовательского интерфейса имеют сходство потоков. – James

3

Проблема работает какой-либо компонент пользовательского интерфейса в потоке без пользовательского интерфейса, как правило, плохо Идея - особенно модальный диалог.

Вместо этого поместите фактический код фоновой обработки в другой поток и после завершения вызова обратно в поток пользовательского интерфейса и запустите диалог сохранения. TPL делает такую ​​вещь очень тривиальной, например.

Task.Factory.StartNew(() => { 
    // do background processing 
}).ContinueWith((task) => { 
    // show save dialog 
}, TaskScheduler.FromCurrentSynchronizationContext()); 
+0

Ошибка: текущий SynchronizationContext не может использоваться в качестве TaskScheduler. – fishmong3r

+1

Похоже, существует [известная проблема] (http://stackoverflow.com/questions/4659257/how-can-synchronizationcontext-current-of-the-main-thread-become-null-in-a-windo?rq = 1) с планировщиком задач в .NET 4.0, который заставляет контекст синхронизации установить значение «null». Следуйте ссылкам для разных обходных путей или обновите до 4.5. – James

+0

Спасибо, проверим это. Тем временем я просто воспользуюсь решением Luaan. – fishmong3r