Entwickler-Ecke

C# - Die Sprache - EventHandler innerhalb von lock aufrufen.


Kasko - Mi 19.06.19 01:21
Titel: EventHandler innerhalb von lock aufrufen.
Servus,

Erstmal der Code, dann die Fragen:


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
using System;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

using AForge.Video.FFMPEG;

namespace VideoPlayer {
  public class Media {
    private object _sync = new object();

    private CancellationTokenSource cts = null;
    private CancellationToken ct;
    private Task task = null;

    private Bitmap _thumbnail;
    private MediaType _type;
    private string _uri;

    public static int VideoThumbnailIndex = 15;

    public event EventHandler<BitmapEventArgs> OnThumbnailChanged = null;

    public Bitmap Thumbnail {
      get {
        lock (_sync)
          return _thumbnail;
      }
      set {
        lock (_sync) {
          _thumbnail = value;
          OnThumbnailChanged?.Invoke(thisnew BitmapEventArgs(_thumbnail));
        }
      }
    }

    public MediaType Type { get; }

    public string Uri {
      get => _uri;
      set {
        SetMediaType(value); 
        _uri = value;

        if (_type == MediaType.Video)
          StartReadingThumbnail();
      }
    }

    private void SetMediaType(string value) {
      if (!System.Uri.IsWellFormedUriString(value, UriKind.RelativeOrAbsolute))
        throw new ArgumentException("Not well formated uri");

      if (-1 != Array.IndexOf(new string[] { ".WAV"".MID"".MIDI"".WMA"".MP3"".OGG"".RMA" }, Path.GetExtension(value).ToUpper()))
        _type = MediaType.Audio;
      else if (-1 != Array.IndexOf(new string[] { ".AVI"".MP4"".DIVX"".WMV" }, Path.GetExtension(value).ToUpper()))
        _type = MediaType.Video;
      else
        throw new NotSupportedException("Not supported file extension");
    }

    private void StartReadingThumbnail() {
      if (cts != null) {
        cts.Cancel();
        task.Wait();
      }

      cts = new CancellationTokenSource();
      ct = cts.Token;

      task = Task.Factory.StartNew(() => {
        VideoFileReader reader = new VideoFileReader();
        reader.Open(_uri);

        for (int i = 1; i < VideoThumbnailIndex && !ct.IsCancellationRequested; i++)
          reader.ReadVideoFrame().Dispose();

        Thumbnail = reader.ReadVideoFrame();
      }, ct);
    }
  }
}


So zu meinen Fragen:

1. Ist der Zugriff auf die Thumbnail-Property sicher vor Multithreading-Exception?
2. Wird der EventHandler im Thread aufgerufen, in dem das Object der Klasse Media erstellt wurde oder im Task, der in der Methode StartReadingThumbnail erstellt wird?


Th69 - Mi 19.06.19 08:44

zu 1: Entsprechend Inconsistently synchronized property [https://help.semmle.com/wiki/display/CSHARP/Inconsistently+synchronized+property] hast du korrekt lock sowohl beim Setter als auch beim Getter eingesetzt.
Bedenke aber, daß dadurch nicht der Zugriff auf Sub-Eigenschaften des Objekts (in deinem Fall Bitmap) gelockt ist, s.a. C# thread safety with get/set [https://stackoverflow.com/questions/505515/c-sharp-thread-safety-with-get-set].

zu 2: Events sind normale Methodenaufrufe und werden immer im aktuellen Thread aufgerufen, d.h. in der StartReadingThumbnail-Methode also im Thread der neu erzeugten Task.


Kasko - Mi 19.06.19 12:23

Okay dann zwei weitere Fragen:

1. Wie kann ich zusätzlich den Zugriff auf die Properties der Bitmap regeln?
2. Wie kann ich den EventHandler im "Erstellungsthread" des Objektes ausführen?


Th69 - Mi 19.06.19 13:02

Auch das mußt du dann synchronisieren (lock).

Es wird nicht gespeichert, von welchem Thread aus ein Objekt erzeugt wurde, d.h. auch das mußt du selber regeln.
Wenn es sich um den UI-Thread handelt, so kannst du Control.Invoke() (bzw. für WPF Dispatcher.Invoke()) dafür benutzen (Die Basisklasse dafür ist SynchronizationContext [https://docs.microsoft.com/de-de/dotnet/api/system.threading.synchronizationcontext?view=netframework-4.8], s.a. Paralleles Computing: Hier geht es allein um SynchronizationContext [https://msdn.microsoft.com/magazine/gg598924.aspx]).
Dem aktuellen Thread kann mittels SynchronizationContext.SetSynchronizationContext [http://msdn.microsoft.com/de-de/library/system.threading.synchronizationcontext.setsynchronizationcontext.aspx] ein SynchronizationContext zugewiesen werden.


Chiyoko - Mi 19.06.19 13:05

Zu 1: (Eventuell) mit volatile als Schlüsselwort. Bin aber nicht 100% sicher, ob sich das auf die gesamte Klasse auswirkt.
Zu 2: z.b. mit SynchronizationContext


Ralf Jansen - Mi 19.06.19 13:21

Du hast eine Referenz auf dein Bitmap rausgegeben. Über das was ab jetzt über die Referenz passiert hast du (bzw. deine VideoPlayer Klasse) keinerlei Kontrolle mehr.
Die Lösung dazu hat @TH69 im ersten Beitrag gepostet. Kurzfassung gib eine Copy des Bitmaps raus. Diese Copy kann man dann beliebig ändern und über den Setter wieder zuweisen.

Das original Bitmap solltest du auch nicht über den OnThumbnailChanged Event rausgeben. Aus dem gleichen Grund.