Как метод может узнать, работает ли он в потоке пользовательского интерфейса? - PullRequest
3 голосов
/ 03 мая 2009

У меня простой вопрос, но я примерно на 80% уверен, что ответ на вопрос будет сопровождаться «вы делаете это неправильно», поэтому я собираюсь задать и непростой вопрос.

Простой вопрос: у меня есть публичный метод публичного класса. Я хочу, чтобы он генерировал исключение, если он вызывается в потоке пользовательского интерфейса. Как я могу это сделать?

Гораздо менее простой вопрос: есть ли более простой способ реорганизовать этот дизайн?

Фон:

Я разработал настольную программу, которая взаимодействует с устаревшим приложением через его API. API не является удаленно поточно-ориентированным. Я построил библиотеку классов, которая инкапсулирует взаимодействие с API (которое включает в себя маршалинг и демаршаллинг данных в огромном буфере byte [], а затем вызывает внешнюю функцию, загруженную из DLL), так что многие детали реализации устаревшего API скрыты из моего кода, насколько это возможно. Поскольку я знал, что создание нескольких экземпляров основного объекта API будет катастрофой, я реализовал его как статический класс.

Я также создал набор классов для запуска заданий в фоновом режиме моего приложения. TaskManager поддерживает очередь из Task объектов и запускает их, используя BackgroundWorker. Использование фонового потока позволяет пользовательскому интерфейсу настольного приложения оставаться отзывчивым во время взаимодействия с устаревшим унаследованным приложением; использование очереди гарантирует, что только одна задача вызывает API в любой момент времени.

К сожалению, я никогда не думал встроить определенные средства защиты в этот дизайн. Недавно я обнаружил места в коде, где я непосредственно вызывал API в потоке пользовательского интерфейса. Я полагаю, что исправил их все, но я хотел бы гарантировать, что я не сделаю это снова.

Если бы я спроектировал это правильно с самого начала, я бы сделал класс обертки API нестатичным, скрыл его конструктор от всего, кроме TaskManager, а затем передал бы экземпляр класса API каждому Task когда оно будет создано. Любому методу, вызываемому Task, который говорил с API, нужно будет передать объект API. Это сделало бы невозможным использование API в основном потоке.

Дело в том, что существует много кода, который взаимодействует с API. Внедрение этого изменения (которое, я думаю, в конечном итоге правильно) затронет все это. Итак, я хотел бы изменить метод API Call, чтобы он вызывал исключение, если он вызывается в потоке переднего плана.

Я знаю, что решаю не ту проблему. Я чувствую это в своих костях. Но я и сейчас довольно увлечен этим и не могу найти правильного решения.

Edit:

Я четко сформулировал вопрос неправильно, поэтому было трудно ответить. Я не должен спрашивать "Как этот метод может знать, работает ли он в потоке пользовательского интерфейса?" Реальный вопрос: «Как этот метод может узнать, работает ли он в потоке неправильный ?» Может (в теории) работать тысяча потоков. Как указывает JaredPar, может быть несколько потоков пользовательского интерфейса. Только один поток является правильным потоком, и его идентификатор легко найти.

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

Ответы [ 3 ]

3 голосов
/ 03 мая 2009

Часть проблемы с определением, являетесь ли вы в потоке пользовательского интерфейса или нет, заключается в том, что может быть более одного потока пользовательского интерфейса. В WPF и WinForms вполне возможно создать более одного потока для отображения пользовательского интерфейса.

В этом случае, похоже, у вас довольно ограниченный сценарий. Лучше всего записать Id потока пользовательского интерфейса или фонового потока в общем местоположении, а затем использовать Thread.CurrentThread.ManagedThreadId, чтобы убедиться, что вы находитесь в правильном потоке.

public class ThreadUtil {
  public static int UIThreadId;

  public static void EnsureNotUIThread() {
    if ( Thread.CurrentThread.ManagedThreadId == UIThreadId ) {
      throw new InvalidOperationException("Bad thread");
    }
  }
}

Этот подход имеет несколько предостережений. Вы должны установить UIThreadId атомарным способом и сделать это до запуска любого фонового кода. Лучше всего добавить следующие строки в вашу программу

Interlocked.Exchange(ref ThreadUtil.UIThreadID, Thread.CurrentThread.ManagedThreadId);

Другой трюк - поиск SynchronizationContext. И WinForms, и WPF установят SynchronizationContext в своих потоках пользовательского интерфейса, чтобы разрешить связь с фоновыми потоками. Для фона, созданного и контролируемого вашей программой (я действительно хочу подчеркнуть этот момент), не будет установлен SynchronizationContext, если вы на самом деле не установите его. Таким образом, следующий код может быть использован в этом очень ограниченном случае

public static bool IsBackground() { 
  return null == SynchronizationContext.Current;
}
1 голос
/ 03 мая 2009

Я бы изменил вариант использования ISynchronizeInvoke и выкинул бы исключение, если ISynchronizeinvoke.InvokeRequired == false. Это позволяет WinForms позаботиться о поиске потока пользовательского интерфейса. Производительность немного отстой, но это сценарий отладки, так что это не имеет значения. Вы даже можете скрыть проверку за флагом #IF DEBUG, чтобы проверять только отладочные сборки.

Вам нужно дать вашему API ссылку на ISynchronizeInvoke - но вы можете легко сделать это при запуске (просто передайте основную форму) или позволить ему использовать статический вызов Form.ActiveForm.

0 голосов
/ 03 мая 2009

Джастин Роджерс подробно обсуждает, как это делает Invoke / BeginInvoke и где это становится безобразным.

По сути, он возвращается к поиску какого-либо окна в потоке пользовательского интерфейса и вызывает GetWindowThreadProcessId , чтобы сравнить текущий идентификатор потока с идентификатором окна. (Конечно, если у вас есть только один поток пользовательского интерфейса, вы можете кэшировать идентификатор потока).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...