0

Here is my code for refrence

public class ProgressChangedObject
    {
        public string Asin { get; set; }
        public string Title { get; set; }
        public DownloadProgress downloadProgress { get; set; }

        public ProgressChangedObject(string asin, string title, DownloadProgress progress)
        {
            Asin = asin;
            Title = title;
            downloadProgress = progress;
        }
    }

public record QueueFile(string asin, string name);

public class BlockingCollectionQueue : IDownloadQueue
    {
        private BlockingCollection<QueueFile> _jobs = new BlockingCollection<QueueFile>();
        public volatile List<QueueFile> queueFiles = new List<QueueFile>();
        private readonly AudibleApi.Api _api;
        private readonly LibraryPath _libraryPath;
        private volatile float percentComplete;
        private volatile QueueFile? currentJob;
        private System.Timers.Timer _timer;

        public BlockingCollectionQueue(AudibleApi.Api api, LibraryPath libraryPath)
        {
            var thread = new Thread(new ThreadStart(OnStart));
            thread.IsBackground = true;
            thread.Start();
            _api = api;
            _libraryPath = libraryPath;
            _timer = new System.Timers.Timer(500);
            _timer.Elapsed += TimerTick;
        }

        ~BlockingCollectionQueue()
        {
            _timer.Dispose();
        }

        private void TimerTick(object? sender, System.Timers.ElapsedEventArgs e)
        {
            if (currentJob != null)
            {
                ProgressChanged?.Invoke(new ProgressChangedObject(currentJob.asin, currentJob.name, new DownloadProgress() { ProgressPercentage = percentComplete }));
            }
        }

        public event Action<ProgressChangedObject>? ProgressChanged;

        public void Enqueue(QueueFile job)
        {
            _jobs.Add(job);
            queueFiles.Add(job);
        }

        private async void OnStart()
        {
            foreach (var job in _jobs.GetConsumingEnumerable(CancellationToken.None))
            {
                _timer.Enabled = true;
                var d = new Progress<DownloadProgress>();
                EventHandler<DownloadProgress> del = (object? sender, DownloadProgress e) => { if (e.ProgressPercentage is not null) { percentComplete = (float)e.ProgressPercentage; currentJob = job; } };
                d.ProgressChanged += del;
                await _api.DownloadAsync(job.asin, _libraryPath, new AsinTitlePair(job.asin, job.name), d);
                d.ProgressChanged -= del;
                _timer.Enabled = false;
                queueFiles.Remove(job);
            }
            
        }

        public List<QueueFile> GetQueue()
        {
            //MessageBox.Show(_jobs.GetConsumingEnumerable().Count().ToString());
            return queueFiles;
        }
    }

I have an instance of this class in my app and when I need to download something I simply add a new queuefile to the queue, the problem is when I subscribe to the Progress changed event like this :

IDownloadQueue downloadQueue = new BlockingCollectionQueue();

downloadQueue.ProgressChanged += OnQueueChanged;

private void OnQueueChanged(ProgressChangedObject obj)
{
    \\ some textblock in my xaml
    myTextBlock.Text = obj.Title;            
}

It throws this error: Exception thrown: 'System.InvalidOperationException' in WindowsBase.dll An exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll but was not handled in user code The calling thread cannot access this object because a different thread owns it.

How can I fix this?

3Dmu
  • 5
  • 3
  • As the error message says: you can manipulate UI objects only from the UI thread. In WPF, one way to do this is to make use of [Dispatcher.BeginInvoke](https://learn.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatcher.begininvoke) to post the progress massage to the UI thread. – Klaus Gütter Apr 14 '22 at 11:02
  • 1
    Does this answer your question? [Accessing UI (Main) Thread safely in WPF](https://stackoverflow.com/questions/11625208/accessing-ui-main-thread-safely-in-wpf) – Klaus Gütter Apr 14 '22 at 11:04

1 Answers1

1

You should use Dispatcher.Invoke Method

You cannot access the GUI thread from a background thread. However, you can use as per below:

        Dispatcher.BeginInvoke(new Action(() =>
        {
            // FooProgressBar.Value = ProgressData();
        }));

For more information visit Threading Model

Reza Heidari
  • 1,192
  • 2
  • 18
  • 23