2010-06-14 12 views
19

Mam wątek tła, który generuje szereg obiektów BitmapImage. Za każdym razem, gdy wątek tła kończy generowanie bitmapy, chciałbym pokazać tę bitmapę użytkownikowi. Problem polega na tym, aby dowiedzieć się, jak przekazać BitmapImage z wątku tła do wątku interfejsu użytkownika.Jak przekazać bitmapimage z wątku tła do wątku interfejsu użytkownika w WPF?

Jest to projekt MVVM, więc moim zdaniem ma Image element:

<Image Source="{Binding GeneratedImage}" /> 

Moim zdaniem model ma właściwość GeneratedImage:

private BitmapImage _generatedImage; 

public BitmapImage GeneratedImage 
{ 
    get { return _generatedImage; } 
    set 
    { 
     if (value == _generatedImage) return; 
     _generatedImage= value; 
     RaisePropertyChanged("GeneratedImage"); 
    } 
} 

Moim zdaniem model posiada również kod tworzy wątek tła:

public void InitiateGenerateImages(List<Coordinate> coordinates) 
{ 
    ThreadStart generatorThreadStarter = delegate { GenerateImages(coordinates); }; 
    var generatorThread = new Thread(generatorThreadStarter); 
    generatorThread.ApartmentState = ApartmentState.STA; 
    generatorThread.IsBackground = true; 
    generatorThread.Start(); 
} 

private void GenerateImages(List<Coordinate> coordinates) 
{ 
    foreach (var coordinate in coordinates) 
    { 
     var backgroundThreadImage = GenerateImage(coordinate); 
     // I'm stuck here...how do I pass this to the UI thread? 
    } 
} 

Chciałbym jakoś przejść backgroundThreadImage do wątku interfejsu użytkownika, gdzie będzie to uiThreadImage, a następnie ustaw GeneratedImage = uiThreadImage, aby można było zaktualizować widok. Przyjrzałem się kilku przykładom dotyczącym WPF Dispatcher, ale nie mogę wymyślić przykładu, który rozwiązuje ten problem. Proszę doradź.

Odpowiedz

12

Następujące wykorzystuje dyspozytora do wykonania delegata akcji w wątku interfejsu użytkownika. Używa modelu synchronicznego, alternatywnie Dispatcher.BeginInvoke wykona delegata asynchronicznie.

var backgroundThreadImage = GenerateImage(coordinate); 

GeneratedImage.Dispatcher.Invoke(
     DispatcherPriority.Normal, 
     new Action(() => 
      { 
       GeneratedImage = backgroundThreadImage; 
      })); 

UPDATE Jak wspomniano w komentarzach, samo powyższe nie zadziała jak BitmapImage nie jest tworzony na wątku UI. Jeśli nie masz zamiaru modyfikować obrazu po utworzeniu go, możesz go zamrozić za pomocą Freezable.Freeze, a następnie przypisać do GeneratedImage w delegacie dyspozytora (BitmapImage staje się tylko do odczytu, a zatem wątki bezpieczne w wyniku Zablokowania). Inną opcją byłoby załadowanie obrazu do obiektu MemoryStream w wątku tła, a następnie utworzenie obrazu bitmapowego w wątku interfejsu użytkownika delegata wysyłającego z tym strumieniem i właściwością strumienia strumieniowego BitmapImage.

+0

Jest to pomocne, Simon, dzięki. Ale jak mogę obejść fakt, że kiedy ten proces się rozpoczyna, "GeneratedImage" nie jest ustawione na instancję obiektu? – devuxer

+1

@DanM, oczywiście, nie myślałem o tym :) Można również uzyskać dyspozytora za pośrednictwem 'Application.Current.Dispatcher' –

+0

Obawiam się, że to też nie robi. Otrzymuję "wątek wywołujący nie może uzyskać dostępu do tego obiektu, ponieważ inny wątek jest jego właścicielem." – devuxer

6

trzeba zrobić dwie rzeczy:

  1. zamrażać BitmapImage tak to może zostać przeniesione do wątku UI, następnie
  2. Użyj dyspozytor przejście do wątku UI, aby ustawić GeneratedImage

Musisz uzyskać dostęp do komunikatora wątku interfejsu użytkownika z wątku generatora. Najbardziej elastyczny sposób, aby to zrobić, aby uchwycić wartość Dispatcher.CurrentDispatcher w głównym wątku i przekazywanie go do wątku generatora:

public void InitiateGenerateImages(List<Coordinate> coordinates) 
{ 
    var dispatcher = Dispatcher.CurrentDispatcher; 

    var generatorThreadStarter = new ThreadStart(() => 
    GenerateImages(coordinates, dispatcher)); 

    ... 

Jeśli wiesz, że będzie korzystać z tego tylko w uruchomionej aplikacji i że Aplikacja będzie miała tylko jeden wątek interfejsu użytkownika, wystarczy zadzwonić pod numer Application.Current.Dispatcher, aby uzyskać bieżącego dyspozytora. Wady:

  1. Utrata umiejętności korzystania z modelu widoku niezależnie od skonstruowanego obiektu aplikacji.
  2. Możesz mieć tylko jeden wątek interfejsu użytkownika w swojej aplikacji.

w wątku generatora dodać połączenie Freeze Kiedy obraz jest generowany, a następnie użyć dyspozytora przejście do wątku interfejsu użytkownika, aby ustawić obraz:

var backgroundThreadImage = GenerateImage(coordinate); 

backgroundThreadImage.Freeze(); 

dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => 
{ 
    GeneratedImage = backgroundThreadImage; 
})); 

Wyjaśnienie

W powyższym kodzie jest krytyczne, aby uzyskać dostęp do Dispatchera.CurrentDispatcher z wątku interfejsu użytkownika, a nie z wątku generatora. Każdy wątek ma swój własny Dispatcher. Jeśli wywołasz Dispatcher.CurrentDispatcher z wątku generatora, otrzymasz jego Dispatchera zamiast tego, który chcesz.

Innymi słowy, trzeba to zrobić:

var dispatcher = Dispatcher.CurrentDispatcher; 

    var generatorThreadStarter = new ThreadStart(() => 
    GenerateImages(coordinates, dispatcher)); 

a nie tak:

var generatorThreadStarter = new ThreadStart(() => 
    GenerateImages(coordinates, Dispatcher.CurrentDispatcher)); 
+0

+1. Dzięki, Ray. Podoba mi się twoja wskazówka, aby przekazać ją do dyspozytora. Koncept Freeze() ma również kluczowe znaczenie, coś, co wiem, że będę potrzebował w przyszłości. (Kiedy skomentowałem Simona, uwolnienie wątku UI tak naprawdę nie było moim celem w tym przypadku - próbowałem tylko zmusić go do aktualizacji (renderowania) każdego obrazu zamiast czekania, aż wszystkie obrazy zostaną przetworzone w celu aktualizacji.) – devuxer

+0

Ray, właśnie wypróbowałem twój pomysł przekazania w dispatcherze, ale z jakiegoś powodu 'Dispatcher.CurrentDispatcher' nie jest równoważny' App.Current.Dispatcher'. Jeśli używam 'App.Current.Dispatcher', mój kod działa idealnie, ale jeśli przejdę w' Dispatcher.CurrentDispatcher', otrzymam "Wątek wywołujący nie może uzyskać dostępu do tego obiektu, ponieważ inny wątek jest jego właścicielem." Jakiś pomysł, dlaczego to może być? – devuxer

+0

Tak, wywołujesz Dispatcher.CurrentDispatcher z wątku generatora, a nie z wątku interfejsu użytkownika. Dodałem wyjaśnienie do odpowiedzi, aby wyjaśnić, jak to naprawić. –

0

w pracach wątek tła ze strumieniami.

Na przykład, w wątku tła:

var artUri = new Uri("MyProject;component/../Images/artwork.placeholder.png", UriKind.Relative); 

StreamResourceInfo albumArtPlaceholder = Application.GetResourceStream(artUri); 

var _defaultArtPlaceholderStream = albumArtPlaceholder.Stream; 

SendStreamToDispatcher(_defaultArtPlaceholderStream); 

w wątku UI:

void SendStreamToDispatcher(Stream imgStream) 
{ 
    dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => 
    { 
     var imageToDisplay = new BitmapImage(); 
     imageToDisplay.SetSource(imgStream); 

     //Use you bitmap image obtained from a background thread as you wish! 
    })); 
} 
Powiązane problemy