Resolved Async method doesn't work

Dragon4ik

Member
Joined
Oct 24, 2020
Messages
16
Programming Experience
Beginner
I've faced with the problem, trying to set a timeout for function execution.

I have the following code:
Function call:
public BitmapImage[,] GetResult(List<BitmapImage> list)
        {
            var task = Task.Run(() => GetBestPuzzleImage(list));
            if (task.Wait(TimeSpan.FromSeconds(10)))
                return task.Result as BitmapImage[,];
            else
                throw new Exception("Timed out");
        }

Method GetBestPuzzleImage() creates big call stack of functions, where parameter list continues be passed in functions, until it gets the final point GetPixels(), where I try to get the format of every list's element. And there is the place, where System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.' is thrown:
C#:
private PixelColor[,] GetPixels(BitmapSource source)
        {
                if (source.Format != PixelFormats.Bgra32)      //here exception is thrown
                {
                    source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0);
                }

                int width = source.PixelWidth;
                int height = source.PixelHeight;
                PixelColor[,] result = new PixelColor[width, height];

                source.CopyPixels(result, width * 4, 0, true);

                return result;
        }

And here is the function call from the UI thread:
C#:
var arr = alghoritm.GetResult(bmp);

I try to use to use Freeze() method to make an object cross-thread in such way:
C#:
private PixelColor[,] GetPixels(BitmapSource source)
{
    source.Freeze();
    var temp = source.Clone();
    
    //and then code works with "temp" value
}

But nothing changed. So I want to know why this problem happens and how to solve it?
 
Solution
If your code is in a Window then the Window itself provides the ability to marshal a call to the UI thread. There's no specific need to use the control you want to affect. Any control created on that same thread will do.

If your code is not directly UI-aware then you can use the SynchronizationContext class. Basically, you initialise a field of that type using SynchronizationContext.Current and you can always use that field to marshal a method call back to the thread that the current object was created on. Obviously, you would need to have created the current object on the UI thread for that to be useful.
In your method : private PixelColor[,] GetPixels(BitmapSource source) you will need to invoke the object.

That object you are trying to access was created elsewhere on another thread. You need to delegate back to the thread that created the object. You can find some sample code here where I explain how to do this : Answered - IF Else statement on background colour of label.

I try to use to use Freeze() method to make an object cross-thread in such way:
Freeze is not used for that purpose. It's for preventing modification to an item if I recall. And you are already prevented enough as it is by that code executing on a new task without locking the object by another method.
 
In your method : private PixelColor[,] GetPixels(BitmapSource source) you will need to invoke the object.

That object you are trying to access was created elsewhere on another thread. You need to delegate back to the thread that created the object. You can find some sample code here where I explain how to do this : Answered - IF Else statement on background colour of label.


Freeze is not used for that purpose. It's for preventing modification to an item if I recall. And you are already prevented enough as it is by that code executing on a new task without locking the object by another method.
I tried your solution , and first, what i realized, that BitmapSource doesn't contain any definition of Invoke() method. So I created PixelDelagate:
C#:
private delegate PixelColor[,] PixelDelegate(BitmapSource el);
And call him in such way:
C#:
private BitmapSource GetRightImage(BitmapSource first, List<BitmapSource> list, ref double totalDifference)
{
            PixelDelegate pixelDelegate = GetPixels;
            PixelColor[,] left = pixelDelegate.Invoke(first);
            //TODO:
}
Maybe I interpretated your solution wrongly, because exception continues being thrown.
 
Note that, these days, you pretty much never have to declare your own delegate types. Just use the appropriate Action (for void methods) or Func. In your case, a Func<BitmapSource, PixelColor[,]> would do the job.
 
If your code is in a Window then the Window itself provides the ability to marshal a call to the UI thread. There's no specific need to use the control you want to affect. Any control created on that same thread will do.

If your code is not directly UI-aware then you can use the SynchronizationContext class. Basically, you initialise a field of that type using SynchronizationContext.Current and you can always use that field to marshal a method call back to the thread that the current object was created on. Obviously, you would need to have created the current object on the UI thread for that to be useful.
 
Solution
Back
Top Bottom