2013-02-02 13 views
5

Ich erhalte diesen Fehler und es sieht so aus, als ob auf dasselbe Bitmap-Objekt von verschiedenen Threads zugegriffen wird. Allerdings benutze ich Schlösser überall damit.Objekt wird derzeit an anderer Stelle verwendet

public class MySingleInstanceClass 
{ 
    private Object locker = new Object(); 

    private Bitmap myImage = new Bitmap(100, 100); 

    public Bitmap MyImage 
    { 
     get 
     { 
      lock (locker) 
       return myImage; 
     } 
     private set 
     { 
      lock (locker) 
       myImage = value; 
     } 
    } 

    private void Refresh() 
    { 
     lock (locker) 
     { 
      var g = Graphics.FromImage(myImage); 
      // do more processing 
     } 
    } 
} 

Klasse MySingleInstanceClass wird nur eine Instanz haben. Anrufe zu MyImage und Refresh() können von verschiedenen Threads kommen. Soweit ich verstehe, wird der Code innerhalb lock(locker) nicht ausgeführt, bis es in einem anderen Thread fertig ist, aber ich bekomme immer noch den Fehler. Kann jemand auf einen Fehler im Code hinweisen?

Ausnahme sieht wie folgt aus:

A first chance exception of type 'System.InvalidOperationException' occurred in System.Drawing.dll

Error: Object is currently in use elsewhere.

at System.Drawing.Graphics.FromImage(Image image)

at (points to the line containing var g = Graphics.FromImage(myImage);)

+0

das Schloss in der Getter/Setter scheint sinnlos .... –

+0

Was denkst du, was du erreichst, wenn du den Getter (und den privaten Setter?) Von MyImage sperrst? Ich denke nur laut nach, also glaube nicht, dass ich die Lösung habe, aber würde es nicht mehr Sinn machen, wenn das Sperrverhalten in der verbrauchenden Klasse dieser Klasse wäre? – bas

+1

@MitchWheat Nun, ich stelle sicher, dass ein Thread, der MyImage aufruft, wartet, bis der Thread, der Refresh() aufruft, den Codeblock dort beendet, andernfalls werde ich myImage zurückgeben, bevor die Verarbeitung abgeschlossen ist. Denkst du, ich brauche es nicht? Warum? –

Antwort

9

das locker Objekt ist nicht statisch; So erstellt jede neue Instanz ein eigenes Schließfach. Sie müssen locker als statisch erstellen, um den Zugriff von anderen Threads zu verhindern, wenn Sie mehrere Objekte verwenden.

Für Einzelobjektszenario ist die Verwendung einer nicht statischen Variablen der Klassenstufe als Schließfach geeignet. Wenn Sie dieses Szenario verwenden, habe ich das Gefühl, dass die Implementierung von Singleton einige Probleme hat.

UPDATE:

public sealed class MySingleInstanceClass 
{ 
    private static volatile MySingleInstanceClass instance; 
    private static object syncRoot = new Object(); 
    private Bitmap myImage; 

    private MySingleInstanceClass() 
    { 
     myImage = new Bitmap(100, 100); 
    } 

    public static MySingleInstanceClass Instance 
    { 
     get 
     { 
      if (instance == null) 
      { 
       lock (syncRoot) 
       { 
        if (instance == null) 
         instance = new MySingleInstanceClass(); 
       } 
      } 

      return instance; 
     } 
    } 

    public Bitmap MyImage 
    { 
     get 
     { 
      lock (syncRoot) 
       return myImage; 
     } 
     private set 
     { 
      lock (syncRoot) 
       myImage = value; 
     } 
    } 

    public void Refresh() 
    { 
     lock (syncRoot) 
     { 
      var g = Graphics.FromImage(myImage); 
      // do more processing 
     } 
    } 

} 
+0

Es gibt nur eine Instanz, und so gibt es nur ein Schließfach (die Klasse ist Singletone) –

+0

dann stimmt der Code, den Sie zur Verfügung gestellt haben, nicht mit der Frage überein? – daryal

+0

@MitchWheat Können Sie erklären, warum ein nicht-statisches Objekt nicht zum Sperren des Zugriffs auf Instanzmitglieder verwendet werden kann? –

3

Es spielt keine Rolle, ob das Objekt, das gesperrt ist statisch oder nicht. Das Problem ist, dass die lock(locker) innerhalb der Getter-Methode entsperrt, sobald die Bitmap zurückgegeben wird. Der zurückgegebene Verweis auf die Bitmap ist nicht durch die Sperre geschützt und kann gleichzeitig mit dem Aufruf von Refresh geändert werden.

Eine mögliche Lösung wäre, das Bitmap selbst zu sperren, aber das kann Deadlocks verursachen, wenn es nicht sorgfältig ausgeführt wird.

+0

Was passiert, wenn ich die Kopie der Bitmap im Getter zurückgeben? –

+0

Wenn Sie 'return myImage' durch' return (Bitmap) myImage.Clone() 'ersetzen, vermeiden Sie auch den Fehler. Aber ohne den Code zu sehen, der MySingleInstanceClass verwendet, ist es schwer zu sagen, ob das eine gute Lösung wäre. – Dirk

+0

Es scheint, ich kann nicht direkt Kommentar zu der anderen angebotenen Lösung MySingleInstanceClass hinzufügen, so dass ich es hier stattdessen schreibe. Stellen Sie sich 2 Fäden vor. Thread 1 ruft nur wiederholt "MySingleInstanceClass.Instance.Refresh()" auf. Thread 2 ruft "MySingleInstanceClass.Instance.MyImage" auf und ändert dann die zurückgegebene Bitmap. Dies verursacht die Ausnahme, die Sie uns in Ihrer Frage gezeigt haben. – Dirk

0

können Sie dieses Bild klonen, bevor es zu Verfahren die beste Lösung war

   Image newimg = (Image)img.Clone(); 
0

In meiner app senden:

  • Kopie dir mit Dateien in ein anderes, tmp. Katalog (mit guid Namen)
  • Verwendung tmp-Dateien pro Benutzer
  • löscht tmp Katalog mit Dateien in den

In meiner App gibt es:

  • jede Anfrage ist 1 min lang
  • max Benutzer ist 120 (Intranet-Anwendung)
  • niemand will warten 5-10 min für raport zu generieren

Kopieren Sie einige Dateien hinzufügen über 0,01-0,2 sek. Für jede Anfrage ist es besser, dass statische Sperre für alle App und Benutzer nicht 10 Minuten warten muss, damit raport generiert wird (10 Benutzer klicken im selben Moment auf "Generate").

 private void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs) 
    { 
     // Get the subdirectories for the specified directory. 
     DirectoryInfo dir = new DirectoryInfo(sourceDirName); 
     DirectoryInfo[] dirs = dir.GetDirectories(); 

     if (!dir.Exists) 
     { 
      throw new DirectoryNotFoundException(
       "Source directory does not exist or could not be found: " 
       + sourceDirName); 
     } 

     // If the destination directory doesn't exist, create it. 
     if (!Directory.Exists(destDirName)) 
     { 
      Directory.CreateDirectory(destDirName); 
     } 

     // Get the files in the directory and copy them to the new location. 
     FileInfo[] files = dir.GetFiles(); 
     foreach (FileInfo file in files) 
     { 
      string temppath = Path.Combine(destDirName, file.Name); 
      file.CopyTo(temppath, false); 
     } 

     // If copying subdirectories, copy them and their contents to new location. 
     if (copySubDirs) 
     { 
      foreach (DirectoryInfo subdir in dirs) 
      { 
       string temppath = Path.Combine(destDirName, subdir.Name); 
       DirectoryCopy(subdir.FullName, temppath, copySubDirs); 
      } 
     } 
    } 


     private void DeleteReportExecutionDirectory(string dirPath) 
    { 
     System.IO.DirectoryInfo downloadedMessageInfo = new DirectoryInfo(dirPath); 
     foreach (FileInfo file in downloadedMessageInfo.GetFiles()) 
     { 
      file.Delete(); 
     } 
     foreach (DirectoryInfo dir in downloadedMessageInfo.GetDirectories()) 
     { 
      dir.Delete(true); 
     } 
     downloadedMessageInfo.Delete(); 
    } 
Verwandte Themen