2009-01-28 9 views
9

Powiedzmy, że mam komponent o nazwie Tasking (którego nie mogę zmodyfikować), który ujawnia metodę "DoTask", która wykonuje niektóre długotrwałe obliczenia i zwraca wynik za pośrednictwem zdarzenia TaskCompleted. Zwykle jest to wywoływane w postaci okna, które użytkownik zamyka po uzyskaniu wyników.Czy jest możliwe umieszczenie programu obsługi zdarzeń w innym wątku dla dzwoniącego?

W moim szczególnym scenariuszu muszę powiązać niektóre dane (rekord bazy danych) z danymi zwróconymi w TaskCompleted i użyć go do aktualizacji rekordu bazy danych.

Sprawdziłem użycie AutoResetEvent, aby powiadomić o zdarzeniu. Problem polega na tym, że AutoResetEvent.WaitOne() zostanie zablokowany, a program obsługi zdarzeń nigdy nie zostanie wywołany. Normalnie AutoResetEvents nazywa się osobnym wątkiem, więc myślę, że oznacza to, że procedura obsługi zdarzeń jest w tym samym wątku, co wywoływana metoda.

Zasadniczo chcę, aby wywołanie asynchroniczne, w którym wyniki są zwracane przez zdarzenie, do wywołania synchronicznego (tj. Wywołanie DoSyncTask z innej klasy), blokując do czasu obsługi zdarzenia, a wyniki umieszczone w lokalizacji dostępne dla zarówno procedura obsługi zdarzeń, jak i metoda, która wywołała metodę, która uruchomiła wywołanie asynchroniczne.

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; 
} 
} 

Muszę tylko powtórzyć to zachowanie w usłudze okno, w którym Application.Run nie nazywa, a obiekt ApplicationContext nie jest dostępna.

+0

Oto dobry film, aby zrozumieć "AutoResetEvent" http://www.youtube.com/watch?v=xaaRBh07N34 – Jaider

Odpowiedz

2

Może uda Ci się uzyskać polecenie DoSyncTask, aby uruchomić obiekt timera, który sprawdza wartość zmiennej danych w odpowiednich odstępach czasu. Gdy dane mają wartość, możesz wtedy mieć inny ogień zdarzenia, aby powiedzieć, że dane mają teraz wartość (i oczywiście wyłączyć zegar).

Dosyć brzydki hack, ale może działać ... w teorii.

Przepraszam, to najlepsze, co mogę wymarzyć z półprzymiem. Czas na łóżko ...

+0

Wygląda na to, że to może zadziałać. Tak jak powiedziałeś, bardzo "hacky" :) –

0

Jeśli Zadanie jest komponentem WinForm, może być bardzo świadomy problemów z wątkami i wywołać procedurę obsługi zdarzeń w głównym wątku - co wydaje się być tym, co widzisz.

Może dlatego, że polega na pompowaniu komunikatów lub coś takiego. Application.Run ma przeciążenia, które są dla aplikacji nie-GUI. Można rozważyć uzyskanie wątku do uruchomienia i pompy, aby sprawdzić, czy to rozwiązuje problem.

Polecam również użycie reflektora, aby zapoznać się z kodem źródłowym komponentu, aby dowiedzieć się, co robi.

0

Prawie już go masz. Potrzebujesz metody DoTask, aby uruchomić na innym wątku, więc wywołanie WaitOne nie uniemożliwi wykonania pracy. Coś takiego:

Action<int, int> doTaskAction = t.DoTask; 
doTaskAction.BeginInvoke(latitude, longitude, cb => doTaskAction.EndInvoke(cb), null); 
taskDone.WaitOne(); 
+0

Wygląda obiecująco. Spróbuję jutro i zagłosuję odpowiednio :) –

+0

Niestety, jeśli podłączyłem obsługę zdarzeń do bieżącego wątku (jak wyżej) i wywołałem funkcję DoTask w innym wątku, to obsługa zdarzeń jest nadal zablokowana przez taskDone.WaitOne() ; –

+0

Czy jest to jakiś składnik COM? Utwórz jego instancję w wątku roboczym, aby nie próbowała przechodzić do wątku STA. –

2

I wypracowane rozwiązanie do async synchronizować problem, przynajmniej z wykorzystaniem wszystkich klas .NET.

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

Nadal nie działa z COM. Podejrzewam, ze względu na gwintowanie STA. Zdarzenie wywoływane przez składnik .NET, który jest hostem OCXa COM, nigdy nie jest obsługiwane przez mój wątek roboczy, dlatego pojawia się zakleszczenie w funkcji WaitOne().

ktoś inny może docenić rozwiązanie chociaż :)

+0

Czy nie byłoby grzecznie podsumować rozwiązania tutaj na SO, używając tych samych uproszczeń jak w pytaniu? – jwg

0

Mój komentarz na odpowiedź Scott W wydaje się trochę tajemnicze po tym, jak ją ponownie przeczytać. Więc pozwól mi być bardziej wyraźne:

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

WaitOne (200) spowoduje to powrót sterowania do wątku UI 5 razy na sekundę (można regulować to jak chcesz). Wywołanie DoEvents() spowoduje opróżnienie kolejki zdarzeń systemu Windows (tej, która obsługuje obsługę wszystkich zdarzeń systemu Windows, takich jak malowanie itp.). Dodaj dwóch członków do swojej klasy (jedna flaga boolowa "done" w tym przykładzie, a jeden z danych powrotnych "street" w twoim przykładzie).

To najprostszy sposób, aby uzyskać to, co chcesz zrobić. (Mam bardzo podobny kod w mojej własnej aplikacji, więc wiem, że to działa)

+0

Oh! Ponadto wartość zwracana przez WaitOne może Ci powiedzieć, czy została zwrócona, ponieważ upłynął limit czasu lub została zasygnalizowana. Możesz użyć tego dla "zrobione". – dviljoen

+0

Nie zrobiłbym tego w ten sposób. Więcej informacji na temat moich problemów można znaleźć pod adresem http://blogs.msdn.com/jfoscoding/archive/2005/08/06/448560.aspx – ollifant

+0

Problem z Application.DoEvents wymaga aplikacji, która jest dostępna tylko dla aplikacji WinForm. Wywoływanie Application.Run itp. W bibliotece klas po prostu nie działa. –

3

Ostatnio miałem pewne problemy z wykonywaniem asynchronicznych wywołań i zdarzeń w wątkach i zwracaniem ich do głównego wątku.

Użyłem SynchronizationContext do śledzenia rzeczy. Poniższy (pseudo) kod pokazuje, co działa dla mnie w danym momencie.

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. 
} 

Mam nadzieję, że to pomoże, ponieważ rozwiązało problem dla mnie.

0

Twój kod jest prawie w porządku ... Właśnie zmienił

t.DoTask(latitude, longitude); 

dla

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

TaskCompleted będą realizowane w tym samym wątku jako DoTask robi. To powinno działać.

Powiązane problemy