2017-03-24 3 views
0

Ich weiß, es gibt eine Menge Informationen über Stackoverflow, aber finde nichts, das mein Problem gelöst.Update UI von einem anderen Thread arbeiten nur einmal

Ich habe ein Programm erstellt, um ffmpeg mit einigen Videodateien zu verwenden. Dieser Prozess kann einige Minuten dauern. Ich versuche also, einen Fortschrittsbalken für ein anderes Formular zu erstellen.

Grundsätzlich, wenn ich auf eine Schaltfläche auf meinem Hauptformular() klicke, wird ein neues Formular angezeigt. Dieses Formular hat nur eine Fortschrittsanzeige und eine Abbrechen-Taste (rufen Sie FormProgress).

Um den ffmpeg auszuführen, verwende ich eine andere Klasse (VideoProcessing), um einen neuen Prozess zu erstellen, ffmpeg auszuführen und den stderror zu überwachen (ffmpeg show progress on stderror). Jedes Mal, wenn ffmpeg einen Fortschritt anzeigt, analysiert diese Klasse die Ausgabe, berechnet den Fortschritt und löst ein Ereignis aus (OnMergeProgress).

Im Grunde ist der Code:

FormSync:

public partial class FormSync : Form 
{ 
    // this form show the progress of job 
    private FormProgress _formProgress; 

    // start the ffmpeg when click on button 
    private void mergeButton_click(object sender, EventArgs e) 
    { 
    var files = new List<string>() {"file1.mp4", "file2.mp4"}; 
    MergeFiles(files); 
    } 

    // join all video files on a single file (using ffmpeg) 
    private void MergeFiles(IEnumerable<string> videoFiles) 
    { 
    // instantiate the class that execute ffmpeg 
    VideoProcessing videoProcessing = new VideoProcessing(); 

    // this class has a a event to show current progress 
    // seconds = total seconds of video (sum length of all video files) 
    // currentSeconds = current progress 
    videoProcessing.OnMergeProgress += (seconds, currentSeconds) => 
    { 
     Invoke((MethodInvoker) delegate() 
     { 
     // Instantiate the form of progress if not visible 
     if (_formProgress = null) 
     { 
      // define the minimum and maximum value of progressbar on constructor 
      _formProgress = new FormProgress(0, seconds); 
      _formProgress.ShowDialog(this); 
     } 

     // update the progress bar value 
     _formProgress.SetProgress(currentSeconds); 
     } 
    } 
    } 
} 

FormProgress:

public partial class FormProgress : Form 
{ 
    public FormProgress(int min, int max) 
    { 
    InitializeComponent(); 
    progressBar.Minimum = min; 
    progressBar.Maximum = max; 
    } 

    public void SetProgress(int value) 
    { 
    value = (value <= progressBar.Minimum) 
     ? progressBar.Minimum 
     : (value >= progressBar.Maximum) ? progressBar.Maximum : value; 

    progressBar.Value = value; 
    Refresh(); 
    } 
} 

VideoProcessing:

internal class VideoProcessing 
{ 
    // Events 
    public delegate void MergeProgressHandler(int totalSeconds, int currentSeconds); 
    public event MergeProgressHandler OnMergeProgress; 

    private int _totalTimeVideos; 

    public void MergeFiles(string[] videoFiles) 
    { 
    // calculate total time of all videos 
    _totalTimeVideos = SomeFunctionToCalculateTotalTime(); 

    // create the Process object to execute FFMPEG (with stdout and stderr redirection) 
    _process = CreateFFMPEGProcess(videoFiles); 
    } 

    // capture the stdout and stderr of ffmpeg 
    private void MergeOutputHandler(object sendingProcess, DataReceivedEventArgs outline) 
    { 
    // capture the current progress 
    // here will go a regex, and some other code to parse the info from ffmpeg 

    // Raise the event 
    OnMergeProgress?.Invoke(_totalTimeVideos, progressSeconds); 
    } 
} 

Grundsätzlich ist die FFMPEG Ausführung und Capture-Verfahren den folgenden Code verwenden: C# execute external program and capture (stream) the output

Das Problem tritt auf, wenn ich versuche, den Code auszuführen.

Wenn ich auf die Schaltfläche que klicken, wird FormProgress angezeigt, aber danach die Statusleiste "einfrieren". Das Programm funktioniert gut, es hängt hier nicht, aber keine Aktualisierung auf Fortschrittsbalken.

Wenn in FormSync, auf InvokeMethod ich den ursprünglichen Code mit folgendem Inhalt ersetzen, ich, dass ffmpeg arbeitet sehen kann, und meine Ereignisse arbeiten zu:

videoProcessing.OnMergeProgress += (seconds, currentSeconds) => 
{ 
    Debug.WriteLine($"{currentSeconds}/{seconds}"); 
} 

das Problem also nicht war ffmpeg oder meine Videoklasse, aber etwas, das die Benutzeroberfläche aktualisiert.

Wenn ich die Invoke wieder ändern, aber diesmal mit dem Debug, wie Code unten, die Debug Druck nur das erste Update, und nichts mehr:

videoProcessing.OnMergeProgress += (seconds, currentSeconds) => 
{ 
    Invoke((MethodInvoker) delegate() { 
    if (_formProgress == null) { 
     _formProgress = new FormProgress(Resources.merging_video_files, 0, seconds); 
     _formProgress.ShowDialog(this); 
    } 

    _formProgress.SetProgress(currentSeconds); 
    }); 
    Debug.WriteLine($"{currentSeconds}/{seconds}"); 
} 

Antwort

1
_formProgress.ShowDialog(this); 

Der Fehler befindet sich hier. ShowDialog() kehrt nicht zurück, bis das Fenster geschlossen ist. Nicht klar, wenn das passiert, aber nicht relevant für den Fehler. Da sie nicht zurückkehrt, ruft Invoke() Deadlocks auf und kann nicht abgeschlossen werden. Was wiederum dazu führt, dass der Worker-Thread hängt.

Teil des Problems ist, dass der Code Invoke() anstelle von Begininvoke() verwendet, hätten Sie den gleichen Fehler nicht bemerkt, wenn Sie die letztere Methode verwendet hätten. Nicht, dass das hübsch wäre, aber es hätte das Problem versteckt. Beachten Sie, dass Sie Invoke() nicht benötigen, Sie brauchen den Rückgabewert nicht und BeginInvoke() funktioniert einwandfrei.

entscheidende Grund, warum Sie diesen Fehler habe ist, weil Sie die ProgressBar.Maximum Eigenschaft initialisieren erforderlich. Tun Sie das nicht, 100 ist ein guter Maximalwert. Nur ein kleines bisschen Mathematik benötigt, Fortschritt ist jetzt (100 * currentSeconds)/Sekunden.

Sie müssen noch Showdialog() nennen, das ist ein wenig umständlich, können Sie das Load-Ereignis verwenden MergeFiles() aufzurufen.

+0

nur einen schnellen Test zu tun, änderte ich 'ShowDialog', zu' show', und alles funktionierte gut. Jetzt Zeit für eine Recherche über Begin Invoke! Tks! (Der Fortschrittsbalken-Maximalwert wird im Formularkonstruktor initialisiert.) –

Verwandte Themen