Я пытаюсь опубликовать простой вопрос здесь, без кода, потому что мой вопрос настолько специфичен: возможно / приемлемо ли изменить SynchronizationContext в методе Async? Если я не установлю SynchronizationContext при запуске метода Async, кажется, что код в нем - включая события, которые я вызываю, и методы в том же модуле класса, который я вызываю, - запускается в том же рабочем потоке. Однако, когда приходит время для взаимодействия с пользовательским интерфейсом, я обнаруживаю, что SynchronizationContext должен быть установлен в поток пользовательского интерфейса.
Можно ли оставить SynchronizationContext установленным для рабочего потока до тех пор, пока я не захочу вызвать вызов функции на основе пользовательского интерфейса?
EDIT:
Чтобы прояснить мой вышеупомянутый вопрос, я нахожусь в безвыходной ситуации с настройкой SynchronizationContext. Если не установить SynchronizationContext, то мои асинхронные операции выполняются в отдельном потоке (по желанию), но тогда я не могу вернуть данные в поток пользовательского интерфейса, не встретив исключение операции между потоками; если я устанавливаю SynchronizationContext для своего потока пользовательского интерфейса, то операции, которые я хочу выполнить в отдельном потоке, в конечном итоге выполняются в потоке пользовательского интерфейса - и затем (конечно) я избегаю исключения между потоками, и все работает. Очевидно, я что-то упустил.
Если вы хотите прочитать больше, я попытался дать очень четкое объяснение того, что я пытаюсь сделать, и я понимаю, что вы тратите свое время, чтобы понять, поэтому спасибо вам за это!
То, что я пытаюсь сделать, показано на этой блок-схеме:

У меня есть приложение Winforms, работающее в потоке пользовательского интерфейса (черным цветом); У меня есть объект Socket, который я хотел бы запустить в своем собственном потоке. Работа класса сокетов заключается в считывании данных из коммуникационного сокета и возврате событий обратно в пользовательский интерфейс при получении данных.
Обратите внимание, что я запускаю цикл сообщений из потока пользовательского интерфейса, чтобы сокет постоянно опрашивался на предмет данных в своем собственном потоке; если данные получены, я хочу обработать эти данные синхронно в том же потоке, не являющемся пользовательским интерфейсом, прежде чем получать больше данных из сокета. (Да, если что-то пойдет не так с каким-либо конкретным сокетом, сокет может остаться с непрочитанными данными.)
Код для запуска цикла сообщений выглядит следующим образом:
if (Socket.IsConnected)
{
SetUpEventListeners();
// IMPORTANT: next line seems to be required in order to avoid a cross-thread error
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
Socket.StartMessageLoopAsync();
}
Когда я запускаю цикл обработки сообщений из потока пользовательского интерфейса, я вызываю асинхронный метод для объекта Socket
:
public async void StartMessageLoopAsync()
{
while (true)
{
// read socket data asynchronously and populate the global DataBuffer
await ReadDataAsync();
if (DataBuffer.Count == 0)
{
OnDataReceived();
}
}
}
Объект Socket
также имеет метод OnDataReceived()
, определенный как:
protected void OnDataReceived()
{
var dataEventArgs = new DataEventArgs();
dataEventArgs.DataBuffer = DataBuffer;
// *** cross-thread risk here!
DataReceived?.Invoke(this, dataEventArgs);
}
Я выделил две области диаграммы с помощью синих звездочек «1» и «2».
В «1» (на диаграмме) я использую шаблон async / await. Я использую сторонний инструмент для сокетов, который не поддерживает Async, поэтому я включил его в свою собственную ReadDataAsync()
функцию, которая выглядит следующим образом:
public override async Task ReadDataAsync()
{
// read a data asynchronously
var task = Task.Run(() => ReadData());
await task;
}
ReadData()
оборачивает метод чтения стороннего компонента: он заполняет глобальный буфер данных, поэтому возвращаемое значение не требуется.
В "2" на диаграмме я сталкиваюсь с перекрестным риском, описанным в моем OnDataReceived()
методе, указанном выше.
Итог: если я установлю SynchronizationContext
, как показано в моем первом фрагменте кода выше, то все в объекте Socket будет работать в своем собственном потоке, пока я не попытаюсь вызвать обработчик события DataReceived; если я закомментирую SynchronizationContext
, то единственная часть кода, которая выполняется в своем собственном потоке, - это краткая сторонняя операция чтения сокетов, заключенная в моем методе DataReadAsync()
.
Поэтому я подумал, могу ли я установить SynchronizationContext
перед попыткой вызова обработчика событий DataReceived. И даже если я "могу", лучший вопрос - хорошая ли это идея? Если я изменю SynchronizationContext
в потоке, не являющемся пользовательским интерфейсом, то мне придется вернуть его к исходному значению после вызова метода DataReceived, и это будет иметь дуро-подобный запах кода для меня.
Есть ли в моем дизайне элегантная настройка или он нуждается в капитальном ремонте? Моя цель состоит в том, чтобы все красные элементы на диаграмме работали в потоке без пользовательского интерфейса, а черные элементы - в потоке пользовательского интерфейса. «2» - это точка, в которой поток без пользовательского интерфейса переходит к потоку пользовательского интерфейса ...
Спасибо.