Многопоточность в Windows Forms - PullRequest
0 голосов
/ 13 октября 2009

Я хочу парализовать 3D-редактор вокселей, созданный поверх Windows Forms, он использует raycaster для рендеринга, поэтому разделение экрана и получение каждого потока в пуле для его рендеринга должно быть тривиальным.

Проблема возникает в том, что поток Windows Forms должен работать как STA - я могу заставить другие потоки запускаться и выполнять работу, но блокировка основного потока при ожидании их завершения вызывает странные случайные взаимоблокировки, как и ожидалось.

Сохранение основного потока разблокированным также было бы проблемой - если, например, пользователь использует инструмент заливки, входные данные будут обрабатываться во время процесса рендеринга, что приведет к появлению промежуточных изображений (объект частично окрашен, для пример). Копирование всего изображения перед каждым кадром также невозможно, поскольку тома достаточно велики, чтобы компенсировать любой выигрыш в производительности, если его нужно копировать в каждом кадре.

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

Если это невозможно, есть ли лучший дизайн для решения этой проблемы?

РЕДАКТИРОВАТЬ: Читая anwsers Я думаю, я не был уверен, что raycaster работает в режиме реального времени, поэтому показ диалогов прогресса не будет работать вообще. К сожалению, FPS достаточно низок (5-40 в зависимости от различных факторов) для ввода между кадрами для получения нежелательных результатов.

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

Ответы [ 5 ]

6 голосов
/ 13 октября 2009

Это распространенная проблема. С окнами формы вы можете иметь только один поток пользовательского интерфейса. Не запускайте свой алгоритм в потоке пользовательского интерфейса, потому что тогда пользовательский интерфейс будет казаться замороженным.

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

Edit:

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

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

  1. Вы не должны рисовать на интерфейсе с каким-либо потоком, кроме потока пользовательского интерфейса. Элементы управления Windows не являются потокобезопасными.
  2. Если у вас много потоков, переключение контекста между ними может снизить производительность.

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

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

1 голос
/ 13 октября 2009

Я что-то упустил или вы можете просто отключить свой элемент управления рендерингом (и любые другие элементы управления, которые генерируют входные события) во время рендеринга кадра? Это предотвратит нежелательные входы.

Если вы по-прежнему хотите принимать события во время рендеринга, но не хотите применять их до следующего кадра, вы должны оставить свои элементы управления включенными и отправить подробные сведения о событии во входную очередь. Затем эта очередь должна обрабатываться в начале каждого кадра.

Это влияет на то, что пользователь все еще может нажимать кнопки и взаимодействовать с пользовательским интерфейсом (поток графического интерфейса не блокируется), и эти события не видны для средства визуализации до начала следующего кадра. При скорости 5 FPS пользователь должен видеть, что его события обрабатываются в наихудшем случае 400 мс (2 кадра), что недостаточно быстро, но лучше, чем многопоточность.

Возможно, что-то вроде этого:

Public InputQueue<InputEvent> = new Queue<InputEvent>();

// An input event handler.
private void btnDoSomething_Click(object sender, EventArgs e)
{ 
    lock(InputQueue)
    {
         InputQueue.Enqueue(new DoSomethingInputEvent());
    }
}

// Your render method (executing in a background thread). 
private void RenderNextFrame()
{
    Queue<InputEvent> inputEvents = new Queue<InputEvent>();

    lock(InputQueue)
    {
         inputEvents.Enqueue(InputQueue.Dequeue());
    }

    // Process your input events from the local inputEvents queue.
    ....

    // Now do your render based on those events.
    ....
}

Да, и делайте рендеринг в фоновом потоке. Ваш поток пользовательского интерфейса драгоценен, он должен выполнять только тривиальную работу. Предложение Мэтта Бранделла о BackgroundWorker имеет много достоинств. Если он не делает то, что вы хотите, ThreadPool также полезен (и проще). Более мощными (и сложными) альтернативами являются CCR или Task Parallel Library .

0 голосов
/ 13 октября 2009

Если вам не нужны все функции, предлагаемые BackgroundWorker, вы можете просто использовать ThreadPool.QueueUserWorkItem, чтобы добавить что-то в пул потоков и использовать фоновый поток. Было бы легко показать некоторый прогресс, когда фоновый поток выполнял свои операции, поскольку вы можете предоставить обратный вызов делегата, чтобы уведомлять вас, когда выполняется конкретный фоновый поток. Взгляните на ThreadPool.QueueUserWorkItem Method (WaitCallback, Object) , чтобы увидеть, на что я вас ссылаюсь. Если вам нужно что-то более сложное, вы всегда можете использовать асинхронный метод APM для выполнения своих операций.

В любом случае, надеюсь, это поможет.

EDIT:

  1. Уведомить пользователя о внесении изменений в пользовательский интерфейс.
  2. В (многих) фоновых потоках, использующих ThreadPool, выполните операции, которые необходимо выполнить для пользовательского интерфейса.
  3. Для каждой операции сохраняйте ссылку на состояние операции, чтобы вы знали, когда она была завершена в WaitCallback. Возможно, поместите их в какой-нибудь тип хэша / коллекции, чтобы сохранить ссылку на них.
  4. Когда операция завершается, удаляйте ее из коллекции, содержащей ссылку на выполненные операции.
  5. После того, как все операции завершены (хэш / коллекция) больше не имеют ссылок в нем, визуализируйте пользовательский интерфейс с примененными изменениями. Или, возможно, постепенно обновлять пользовательский интерфейс

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

Хотя я не специалист по потокам, просто пытаюсь обдумать это сам. Надеюсь, это поможет.

0 голосов
/ 13 октября 2009

Копирование всего изображения перед каждым кадром также невозможно, поскольку тома достаточно велики, чтобы компенсировать любой выигрыш в производительности, если его нужно копировать в каждом кадре.

Тогда не копируйте внеэкранный буфер на каждый кадр.

0 голосов
/ 13 октября 2009

Показать модальное диалоговое окно «Пожалуйста, подождите», используя ShowDialog, затем закройте его, когда закончите рендеринг.

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

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