Home > Software engineering >  How to implement Multithreading in MVVM C# WPF?
How to implement Multithreading in MVVM C# WPF?

Time:01-16

I am trying to design an application in WPF C# and design pattern MVVM. The application is supposed to take camera feed from dll and cs file provided with camera. How should I implement this in MVVM pattern.

SDK is provided with the camera in which they have used Dispatcher.invoke() for taking camera feed and other camera function but that sdk is single window STA application. I tried using the same technique with MVVM but the was unable to update the camera Feed in view from view model. i I tried binding imageSource of image brush with bitmap property in view model.

xaml Code :

 <Grid>

    <Border CornerRadius="10" Margin="10,10,10,10">
        <Border.Background>
            <ImageBrush ImageSource="{Binding ImageSource}"/>
        </Border.Background>
    </Border>

</Grid>

xaml code behind :

public HomePage()
    {
        InitializeComponent();

        this.DataContext = new HomePageViewModel();
    }

HomeViewModel :

public class HomePageViewModel : INotifyPropertyChanged
{


    camera? craam_ = null;
    WriteableBitmap? bmp_ = null;

    WriteableBitmap? imageSource;


    public WriteableBitmap ImageSource
    {
        get { return imageSource; }
        set
        {
            imageSource = value;
            OnPropertyChanged(nameof(ImageSource));

        }
    }



    public HomePageViewModel()
    {
       
        cam_ = camera.Open(camera.EnumV2()[0].id);
        CameraInit();
    }


    void CameraInit()
    {

        int width = 0, height = 0;

        if (cam_ != null)
        {
            if (cam_.get_Size(out width, out height))
            {

                bmp_ = new WriteableBitmap(width, height, 0, 0, PixelFormats.Bgr32, null);
                ImageSource = new WriteableBitmap(width, height, 0, 0, PixelFormats.Bgr32, null);
                cam_.StartPullModeWithCallback(new camera.DelegateEventCallback(DelegateEventCallback));
            }



        }


    }

    private void DelegateEventCallback(camera.eEVENT nEvent)
    {
        Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => { OnEventImage(); }));
    }

    private void OnEventImage()
    {
        if (bmp_ != null)
        {
            camera.FrameInfoV3 info = new camera.FrameInfoV3();
            bool bOK = false;
            try
            {
                bmp_.Lock();
                try
                {
                    bOK = cam_.PullImageV3(bmp_.BackBuffer, 0, 32, bmp_.BackBufferStride, out info); // check the return value
                    bmp_.AddDirtyRect(new Int32Rect(0, 0, bmp_.PixelWidth, bmp_.PixelHeight));
                }
                finally
                {
                    bmp_.Unlock();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }

            if (bOK)
                ImageSource = bmp_;


        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;
    public void OnPropertyChanged(string PropertyName ) {

        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
    
    }
}

CodePudding user response:

DelegateEventCallback might be called on any thread, so at this time Dispatcher.CurrentDispatcher is not the dispatcher associated with the UI thread.

So better initialize a field in the constructor (which is called on the UI thread in your case):

private Dispatcher _dispatcher;

public HomePageViewModel()
{
    _dispatcher = Dispatcher.CurrentDispatcher;
    cam_ = camera.Open(camera.EnumV2()[0].id);
    CameraInit();
}

and use this:

private void DelegateEventCallback(camera.eEVENT nEvent)
{
    _dispatcher.BeginInvoke(new Action(() => { OnEventImage(); }));
}

Unrelated: better remove the MessageBox from the OnEventImage code. If something goes wrong this will be called for every frame; might look funny.

CodePudding user response:

I don't have one of those cameras or know what some of those variables are. I do, however, know about writeable bitmap and threading.

Edit:

It seems a factor in your immediate problem might be caused by the way the dispatcher queue works. Everything the ui does is handled by this queue and it is not a FIFO queue, it's a priority queue.

When you notify property changed, the data transfer necessary from viewmodel to view is driven by an item added to the dispatcher queue.

Once that data transfers it will invalidate some UI and the UI will need to re render. It adds an item to the dispatcher queue.

That rendering item has a lower priority than data transfer.

If you fire property changed in a tight loop then the UI thread will process one, go back to the queue and find another data transfer is top of the queue. It will process that. And so on. The re-render item will never get to the top of the queue until you pause everything.

Another factor is likely running some of that code you have there on the UI thread.

That is probably burning cycles as well. Expensive processing on the UI thread would be bad here.


If you really just want to show a video, I am doubtful MVVM is adding anything but overhead here.

Maybe it would be far simpler just to go with the working code which is purely ui based and works.


If you want mvvm for whatever reason.

I think you might have to ensure as much processing as possible really does take place on a background thread.

Writeable bitmap is a freezable and you should call .Freeze() on it once you've built your picture. That makes memory use more efficient and allows you to pass it from a background thread to the UI thread.

I've not tried it myself but bitmapsource.create is likely worth exploring rather than writeable bitmap if you try creating anew each frame.

You can then run your code which grabs that frame in a background thread in a loop or event handler. That is, assuming whatever that camera stuff is, doesn't have thread affinity.

Exactly how best to do that depends on the camera software. We don't know what that is of course.

Dispatcher is itself a queue but if your performance is poor you might consider a blocking queue. Run multiple image processing threads which freeze and then add their output bitmap source to the blocking queue. Subscribe to that queue on the UI thread and set your property.

Unless you're creating extra sta threads ( usually a bad idea ) I think application.current.dispatcher is your only dispatcher. You can reference that from anywhere without referencing UI once your Wpf app starts.

  • Related