Можно ли поместить обработчик событий в другой поток, вызывающий? - PullRequest
9 голосов
/ 28 января 2009

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

В моем конкретном сценарии мне нужно связать некоторые данные (запись базы данных) с данными, возвращенными в TaskCompleted, и использовать их для обновления записи базы данных.

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

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

public class SyncTask
{
    TaskCompletedEventArgs data;
    AutoResetEvent taskDone;

public SyncTask()
{
    taskDone = new AutoResetEvent(false);
}

public string DoSyncTask(int latitude, int longitude)
{
    Task t = new Task();
    t.Completed = new TaskCompletedEventHandler(TaskCompleted);
    t.DoTask(latitude, longitude);
    taskDone.WaitOne(); // but something more like Application.DoEvents(); in WinForms.
    taskDone.Reset();
    return data.Street;
}

private void TaskCompleted(object sender, TaskCompletedEventArgs e)
{
    data = e;
    taskDone.Set(); //or some other mechanism to signal to DoSyncTask that the work is complete.
}
}

In a Windows App the following works correctly.

public class SyncTask
{
    TaskCompletedEventArgs data;

public SyncTask()
{
    taskDone = new AutoResetEvent(false);
}

public string DoSyncTask(int latitude, int longitude)
{
    Task t = new Task();
    t.Completed = new TaskCompletedEventHandler(TaskCompleted);
    t.DoTask(latitude, longitude);
    while (data == null) Application.DoEvents();

    return data.Street;
}

private void TaskCompleted(object sender, TaskCompletedEventArgs e)
{
    data = e;
}
}

Мне просто нужно повторить это поведение в оконной службе, где Application.Run не вызывается и объект ApplicationContext недоступен.

Ответы [ 7 ]

3 голосов
/ 04 февраля 2009

В последнее время у меня возникли проблемы с асинхронными вызовами и событиями в потоках и их возвратом в основной поток.

Я использовал SynchronizationContext , чтобы отслеживать вещи. (Псевдо) код ниже показывает, что работает для меня в данный момент.

SynchronizationContext context;

void start()
{
    //First store the current context
    //to call back to it later
    context = SynchronizationContext.Current; 

    //Start a thread and make it call
    //the async method, for example: 
    Proxy.BeginCodeLookup(aVariable, 
                    new AsyncCallback(LookupResult), 
                    AsyncState);
    //Now continue with what you were doing 
    //and let the lookup finish
}

void LookupResult(IAsyncResult result)
{
    //when the async function is finished
    //this method is called. It's on
    //the same thread as the the caller,
    //BeginCodeLookup in this case.
    result.AsyncWaitHandle.WaitOne();
    var LookupResult= Proxy.EndCodeLookup(result);
    //The SynchronizationContext.Send method
    //performs a callback to the thread of the 
    //context, in this case the main thread
    context.Send(new SendOrPostCallback(OnLookupCompleted),
                 result.AsyncState);                         
}

void OnLookupCompleted(object state)
{
    //now this code will be executed on the 
    //main thread.
}

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

2 голосов
/ 30 января 2009

Я разработал решение проблемы асинхронной синхронизации, по крайней мере, используя все классы .NET.

http://geekswithblogs.net/rgray/archive/2009/01/29/turning-an-asynchronous-call-into-a-synchronous-call.aspx

Это все еще не работает с COM. Я подозреваю, что из-за потоков STA. Событие, создаваемое компонентом .NET, в котором размещается COM OCX, никогда не обрабатывается моим рабочим потоком, поэтому я захожу в тупик в WaitOne ().

кто-то может оценить решение, хотя:)

2 голосов
/ 28 января 2009

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

Довольно уродливый хак, но это может сработать ... теоретически.

Извините, это лучшее, что я могу придумать, полусонный. Время спать ...

0 голосов
/ 10 января 2013

Ваш код почти прав ... Я только что изменил

t.DoTask(latitude, longitude);

для

new Thread(() => t.DoTask(latitude, longitude)).Start();

TaskCompleted будет выполняться в том же потоке, что и DoTask. Это должно работать.

0 голосов
/ 03 февраля 2009

Мой комментарий к ответу Скотта В. кажется немного загадочным после того, как я перечитал его. Итак, позвольте мне быть более точным:

while( !done )
{
    taskDone.WaitOne( 200 );
    Application.DoEvents();
}

WaitOne (200) заставит его возвращать управление потоку пользовательского интерфейса 5 раз в секунду (вы можете настроить его по своему усмотрению). Вызов DoEvents () очистит очередь событий Windows (которая обрабатывает все события Windows, такие как рисование и т. Д.). Добавьте в свой класс двух членов (в этом примере один флаг bool "done" и один в вашем примере возвращающие данные "street").

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

0 голосов
/ 28 января 2009

Вы почти получили это. Вам нужно, чтобы метод DoTask выполнялся в другом потоке, поэтому вызов WaitOne не помешает выполнению работы. Примерно так:

Action<int, int> doTaskAction = t.DoTask;
doTaskAction.BeginInvoke(latitude, longitude, cb => doTaskAction.EndInvoke(cb), null);
taskDone.WaitOne();
0 голосов
/ 28 января 2009

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

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

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

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