Проблема обновления изображения в конвейере потока данных TPL

Я использую TPL Dataflow для загрузки видео (я использую библиотеку Emgu.CV для загрузки) с пути и через TPL Dataflow сначала рисую его в приложении Windows Form (после этого будет этап обмена данными между доской). У меня было еще одно сообщение, которое очень помогло мне с потоком данных TPL здесь: Асинхронная задача, буферизация видео

Но после настройки потока данных TPL первое изображение загружается только в графический интерфейс, а после этого (в то время как блок работает, потому что отпечатки отображаются в cmd) изображение не обновляется ... Я не могу понять, что не так? Это связано с планировщиком или с потоком данных TPL? Ниже приведен код:

public async void CreateVideoProcessingNetwork()
{
    string video_path = @"C:\.......\video_640x360_360p.mp4";

    /* displayVideo Block*/
    var display_video = new ActionBlock<Bitmap>(async received_bitmap =>
    {
        Console.WriteLine("Inside display_video");

        PicturePlot2.Refresh();
        PicturePlot2.Image = received_bitmap;
        Console.WriteLine("Image size = " + received_bitmap.Size);
        Console.WriteLine("Image width = " + received_bitmap.Width);

        await Task.Delay(30);
   });

    var loadVideo = new ActionBlock<string>(async path =>
    {
        capture = new VideoCapture(path);
        Mat matrix = new Mat();
        capture.Read(matrix);
        var mem_stream = new MemoryStream();
        Bitmap HWimage;

        while (matrix.Rows != 0 && matrix.Width != 0)
        {
            Console.WriteLine("Inside LoadVideo");
            matrix = new Mat();
            capture.Read(matrix);
            Bitmap bitmap = new Bitmap(matrix.Width, matrix.Rows);
            bitmap = matrix.ToBitmap();
            bitmap.Save(mem_stream, System.Drawing.Imaging.ImageFormat.Jpeg);
            byte[] image_array = mem_stream.ToArray();
            Console.WriteLine("image_array = " + image_array.Length);
            using (var mem_stream_hw = new MemoryStream(image_array)) HWimage = new Bitmap(mem_stream_hw);
            var accepted = await display_video.SendAsync(HWimage);
            if (!accepted) break;
            await Task.Delay(25);
        }
    });

    PropagateCompletion(loadVideo, display_video);

    loadVideo.Post(video_path);
    loadVideo.Complete();
    await display_video.Completion;
}

Я что-то не так понимаю? Я хотел бы сделать какой-то конвейер в отображении видео через TPL Dataflow.

ОБНОВИТЬ:

После того, как я изменил упомянутые видео, воспроизводится. Но есть еще одна проблема. Скорость создания данных производителем выше (на самом деле это зависит от времени Thread.Sleep в TransformManyBlock), и после нескольких секунд воспроизведения видео приложение вылетает со следующей ошибкой:

Unhandled Exception: System.Runtime.InteropServices.ExternalException: A generic error occurred in GDI+.
   at System.Drawing.Graphics.MeasureString(String text, Font font, SizeF layoutArea, StringFormat stringFormat)
   at System.Drawing.Graphics.MeasureString(String text, Font font, Int32 width)
   at System.Windows.Forms.ThreadExceptionDialog..ctor(Exception t)
   at System.Windows.Forms.Application.ThreadContext.OnThreadException(Exception t)
   at System.Windows.Forms.Control.WndProcException(Exception e)
   at System.Windows.Forms.Control.ControlNativeWindow.OnThreadException(Exception e)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
   at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
   at System.Windows.Forms.Application.Run(Form mainForm)
   at ntComlabGUI.Program.Main()

Если, например, удалить Thread.Sleep, ошибка возникает практически сразу (в первые 1-2 секунды). Как можно управлять потоком? В сообщении Асинхронная задача, буферизация видео упоминается метод BoundedCapacity. но я попробовал, и ошибка все еще существует. Ниже приведен код:

public async void CreateVideoProcessingNetwork()
{
    //string video_path = @"C:\Projects_Repo\ComlabDMA_Ethernet_video\ntComlabGUI_Ultrascale_ethernet\ntComlabGUI\video_640x360_360p.mp4";
    string video_path = @"C:\Projects_Repo\ComlabDMA_Ethernet_video\ntComlabGUI_Ultrascale_ethernet\ntComlabGUI\video_640x360_360p.mp4";


    /* Video Loading TPL Block */
    var video_loader = new TransformManyBlock<string, Bitmap>(load_video,
        new ExecutionDataflowBlockOptions { BoundedCapacity = 128 });

    IEnumerable<Bitmap> load_video(string path)
    {

        capture = new VideoCapture(path);
        Mat matrix = new Mat();
        capture.Read(matrix);
        var mem_stream = new MemoryStream();


        while (matrix.Rows != 0 && matrix.Width != 0)
        {
            capture.Read(matrix);
            Bitmap bitmap = new Bitmap(matrix.Width, matrix.Rows);

            bitmap = matrix.ToBitmap();

            yield return bitmap;
            //Thread.Sleep(1);

        }
        yield break;
    }



    /* Video Loading TPL Block */

    var display_video = new ActionBlock<Bitmap>(async received_image =>
    {
        PicturePlot2.Image = received_image;
        await Task.Delay(33);
    },
    new ExecutionDataflowBlockOptions()
    {
        TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(),
        BoundedCapacity = 128
    });



    var linkOptions = new DataflowLinkOptions { PropagateCompletion = true };

    video_loader.LinkTo(display_video, linkOptions);
    video_loader.Post(video_path);
    video_loader.Complete();
    await display_video.Completion;


}

Возможно, следующая ссылка является решением? : Как организовать управление потоком в потоках данных TPL?

Заранее благодарим за помощь, а также за быстрый ответ, очень признательны!


person ThKont    schedule 15.07.2021    source источник
comment
Я удивлен, что показано даже одно изображение. Вы не можете изменить пользовательский интерфейс из другого потока. Период. Блоки записываются так же, как и Task.Run, а не как блоки в конвейере. Вместо использования явного кода в одном блоке для отправки в другой вы должны использовать TransformBlock или TransformManyBlock и направлять его вывод в следующий блок с помощью LinkTo. Нет необходимости в явном PropagateCompletion   -  person Panagiotis Kanavos    schedule 15.07.2021
comment
Вы можете использовать блок для изменения пользовательского интерфейса, если вы укажете, что он должен выполняться в потоке пользовательского интерфейса с свойство DataflowBlockOptions.TaskScheduler   -  person Panagiotis Kanavos    schedule 15.07.2021
comment
Что пытается сделать код цикла? Почему бы не использовать результат ToBitmap() напрямую? Или еще лучше отправить Mat в следующий блок?   -  person Panagiotis Kanavos    schedule 15.07.2021
comment
@PanagiotisKanavos, ты прав. Я не знаю, зачем я делал MemoryStream. Это было бесполезно, и в этом была проблема. Цикл заключается в загрузке изображений до тех пор, пока видео не закончится. Что касается первого комментария, Теодор также порекомендовал мне способ, который вы предложили, поэтому я перейду на способ TransformManyBlock. Но этот способ тоже работает. Итак, как мне обращаться с блоками в другом потоке? Насколько я понимаю, в настоящий момент я изменяю пользовательский интерфейс из блоков, которые находятся в одном потоке. Было бы лучше иметь другую ветку для блоков? И как тогда мне общаться с UI?   -  person ThKont    schedule 15.07.2021
comment
Привет @ThKont. Добавление дополнительного вопроса в вопрос, на который уже дан ответ, здесь не одобряется. Рекомендуемый курс действий - задать новый вопрос!   -  person Theodor Zoulias    schedule 16.07.2021


Ответы (1)


Скорее всего, проблема в том, что компонент PicturePlot2 не любит манипулировать потоками, не относящимися к пользовательскому интерфейсу. Чтобы гарантировать, что делегат ActionBlock будет вызываться в потоке пользовательского интерфейса, вы можете настроить _ 3_ блока следующим образом:

var display_video = new ActionBlock<Bitmap>(async received_bitmap =>
{
    Console.WriteLine("Inside display_video");

    PicturePlot2.Refresh();
    PicturePlot2.Image = received_bitmap;
    Console.WriteLine("Image size = " + received_bitmap.Size);
    Console.WriteLine("Image width = " + received_bitmap.Width);

    await Task.Delay(30);
}, new ExecutionDataflowBlockOptions()
{
    TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

Чтобы это работало, требуется, чтобы блок был создан в потоке пользовательского интерфейса. Это потому, что приложения Windows Form устанавливают специальный специальный SynchronizationContext в потоке пользовательского интерфейса при их запуске, и нам нужен _ 6_ для захвата этого контекста.

person Theodor Zoulias    schedule 15.07.2021
comment
Между прочим, большое спасибо вам обоим, ребята! Очень быстрый ответ! - person ThKont; 15.07.2021