2010-06-14 11 views
19

Ich habe einen Hintergrund Thread, der eine Reihe von BitmapImage Objekte erzeugt. Jedes Mal, wenn der Hintergrund-Thread die Erstellung einer Bitmap beendet, möchte ich diese Bitmap dem Benutzer zeigen. Das Problem besteht darin herauszufinden, wie Sie die BitmapImage aus dem Hintergrundthread an den UI-Thread übergeben.Wie übergibt man ein BitmapImage von einem Hintergrundthread an den UI-Thread in WPF?

Dies ist ein MVVM Projekt, so meine Ansicht hat ein Image Element:

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

My view-Modell verfügt über eine Eigenschaft GeneratedImage:

private BitmapImage _generatedImage; 

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

Meine Ansicht-Modell auch den Code hat, dass erzeugt den Hintergrundthread:

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

möchte ich irgendwie weitergeben backgroundThreadImage auf dem UI-Thread, wo es uiThreadImage werden wird, stellen Sie dann GeneratedImage = uiThreadImage so die Ansicht aktualisieren. Ich habe mir einige Beispiele zum WPF Dispatcher angeschaut, aber ich kann kein Beispiel finden, das dieses Problem angeht. Bitte beraten.

Antwort

12

Nachfolgend verwendet das Dispatcher einen Aktionsdelegate auf dem UI-Thread auszuführen. Dies verwendet ein synchrones Modell, die Alternative Dispatcher.BeginInvoke führt den Delegaten asynchron aus.

var backgroundThreadImage = GenerateImage(coordinate); 

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

UPDATE Wie in den Kommentaren diskutiert, allein die oben wird als BitmapImage nicht funktionieren wird, auf dem UI-Thread nicht erstellt werden. Wenn Sie nicht die Absicht, zu modifizieren das Bild haben, wenn Sie es erstellt haben, können Sie es Freezable.Freeze mit einzufrieren und dann zu GeneratedImage in der Dispatcher-Delegat zuweisen (die Bitmap wird schreibgeschützt und somit THREAD als Folge der Freeze-Funktion). Die andere Option wäre, das Bild in einen MemoryStream im Hintergrundthread zu laden und dann BitmapImage im Benutzeroberflächenthread im Dispatcher-Delegaten mit diesem Stream und der StreamSource-Eigenschaft von BitmapImage zu erstellen.

+0

Dies ist hilfreich, Simon, danke. Aber wie komme ich dazu, dass 'GeneratedImage' nicht auf eine Instanz eines Objekts gesetzt wird, wenn dieser Prozess beginnt? – devuxer

+1

@DanM natürlich habe ich nicht darüber nachgedacht :) Sie können auch einen Dispatcher über 'Application.Current.Dispatcher' erreichen –

+0

Ich fürchte, das tut es auch nicht. Ich bekomme "Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da ein anderer Thread es besitzt." – devuxer

6

Sie müssen zwei Dinge tun:

  1. Lassen Sie Ihre Bitmap so kann es auf dem UI-Thread verschoben werden, dann
  2. den Dispatcher verwenden, um den UI-Thread, um den Übergang der GeneratedImage
einstellen

Sie werden den UI-Thread Dispatcher vom Generator Thread zugreifen müssen. Die flexibelste Weg, dies zu tun ist, die den Wert Dispatcher.CurrentDispatcher im Hauptthread zu erfassen und es in den Generator Thread vorbei:

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

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

    ... 

Wenn Sie wissen, dass Sie nur diese innerhalb einer laufenden Anwendung verwenden wird, und dass die Anwendung wird nur einen UIhread haben, Sie können einfach Application.Current.Dispatcher aufrufen, um den aktuellen Dispatcher zu erhalten. Die Nachteile sind:

  1. Sie verlieren die Möglichkeit, Ihr Ansichtsmodell unabhängig von einem konstruierten Anwendungsobjekt zu verwenden.
  2. Sie können nur ein UI-Thread in Ihrer Anwendung haben.

Im Generator Thread, fügt einen Anruf zu Frieren, nachdem das Bild erzeugt wird, dann das Dispatcher verwenden, um den UI-Thread zu überführen, das Bild zu setzen:

var backgroundThreadImage = GenerateImage(coordinate); 

backgroundThreadImage.Freeze(); 

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

Klärungs

Im obigen Code ist es wichtig, dass auf Dispatcher.CurrentDispatcher vom Benutzeroberflächen-Thread aus und nicht vom Generator-Thread zugegriffen wird. Jeder Thread hat seinen eigenen Dispatcher. Wenn Sie Dispatcher.CurrentDispatcher vom Generatorthread aufrufen, erhalten Sie den Dispatcher anstelle des gewünschten Dispatcher.

Mit anderen Worten: Sie müssen dies tun:

var dispatcher = Dispatcher.CurrentDispatcher; 

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

und nicht diese:

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

+1. Danke, Ray. Ich mag Ihren Tipp im Dispatcher. Das Freeze() -Konzept ist auch kritisch, etwas, von dem ich weiß, dass ich es in Zukunft brauchen werde. (Wie ich Simon bereits gesagt habe, war die Freigabe des UI-Threads in diesem Fall sowieso nicht mein Ziel - ich habe nur versucht, jedes Bild zu aktualisieren (zu rendern), anstatt zu warten, bis alle Bilder aktualisiert wurden.) – devuxer

+0

Ray, ich habe gerade versucht, den Dispatcher zu übergeben, aber aus irgendeinem Grund ist 'Dispatcher.CurrentDispatcher' nicht äquivalent zu' App.Current.Dispatcher'. Wenn ich 'App.Current.Dispatcher' verwende, funktioniert mein Code perfekt, aber wenn ich' Dispatcher.CurrentDispatcher' übergebe, bekomme ich "Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, weil ein anderer Thread es besitzt." Irgendeine Idee warum das sein könnte? – devuxer

+0

Ja, Sie rufen Dispatcher.CurrentDispatcher vom Generatorthread und nicht vom UI-Thread auf. Ich habe der Antwort eine Klarstellung hinzugefügt, um zu erklären, wie sie zu beheben ist. –

0

Im Hintergrund-Thread Arbeit mit Streams.

Zum Beispiel im Hintergrund-Thread:

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

StreamResourceInfo albumArtPlaceholder = Application.GetResourceStream(artUri); 

var _defaultArtPlaceholderStream = albumArtPlaceholder.Stream; 

SendStreamToDispatcher(_defaultArtPlaceholderStream); 

Im UI-Thread:

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! 
    })); 
} 
Verwandte Themen