Every time when timer invoke UpdateDocumentsListFromServer UI freezes for 3 seconds. How to update list in async style under .net 3.5?
ViewModel:
public class ShippingDocumentsRegisterViewModel : ViewModelBase
{
ShippingDocumentsModel model = new ShippingDocumentsModel();
DispatcherTimer timer = new DispatcherTimer();
BackgroundWorker BW = new BackgroundWorker();
public ShippingDocumentsRegisterViewModel()
{
timer = new DispatcherTimer();
timer.Tick += new EventHandler(UpdateDocumentsListFromServer);
timer.Interval = new TimeSpan(0, 0, 10);
timer.Start();
this.Columns = model.InitializeColumns();
BW.DoWork += UpdateDocumentsList;
BW.RunWorkerAsync();
}
public void UpdateDocumentsList(object o, EventArgs args)
{
this.ShippingDocuments = model.GetDocuments();
}
public void UpdateDocumentsListFromServer(object o, EventArgs args)
{
// Taking a lot of time. How to do it async?
var tempDocuments = model.GetDocumentsFromServer();
foreach (var item in tempDocuments)
{
this.shippingDocuments.Add(item);
}
//
}
private ObservableCollection<ShippingDocument> shippingDocuments;
public ObservableCollection<ShippingDocument> ShippingDocuments
{
get
{
return shippingDocuments;
}
private set
{
shippingDocuments = value;
RaisePropertyChanged("ShippingDocuments");
}
}
public ObservableCollection<ShippingDocumentColumDescriptor> Columns { get; private set; }
}
GetDocumentsFromServer look like
public ObservableCollection<ShippingDocument> GetDocumentsFromServer()
{
System.Threading.Thread.Sleep(3000);
return new ObservableCollection<ShippingDocument> { new ShippingDocument { Name = "Test" } };
}
You could also use a background worker that reports progress to the UI
public ShippingDocumentsRegisterViewModel()
{
BW.DoWork += UpdateDocumentsListFromServer;
BW.RunWorkerCompleted += BW_RunWorkerCompleted;
BW.WorkerReportsProgress = true;
BW.ProgressChanged += UpdateGui;
BW.RunWorkerAsync();
}
public void UpdateGui(object o, EventArgs args)
{
foreach (var item in tempDocuments)
{
this.shippingDocuments.Add(item);
}
}
public void UpdateDocumentsListFromServer(object o, EventArgs args)
{
while (true) {
System.Threading.Thread.Sleep(3000);
tempDocuments = GetDocumentsFromServer();
BW.ReportProgress(0);
}
}
int num = 0;
public ShippingDocument[] GetDocumentsFromServer()
{
System.Threading.Thread.Sleep(3000);
return new ShippingDocument[1] { new ShippingDocument { Name = "Test" + num++} };
}
private ShippingDocument[] tempDocuments = new ShippingDocument[0];
Just offload it to a new thread using Task and Async/Await like so:
public async void UpdateDocumentsListFromServer(object o, EventArgs args)
{
// This will execute async and return when complete
await Task.Run(()=>{
var tempDocuments = model.GetDocumentsFromServer();
foreach (var item in tempDocuments)
{
this.shippingDocuments.Add(item);
}
});
//
}
Keep in mind this updates on a different thread then the UI. So it is not allowed to touch anything on the UI thread or you will get threading issues. So if shippingDocuments was created on the UI thread and is not thread-safe you could instead return a collection of items then add them:
public async void UpdateDocumentsListFromServer(object o, EventArgs args)
{
// Execute on background thread and put results into items
var items = await Task.Run(()=>{
var tempDocuments = model.GetDocumentsFromServer();
return tempDocuments;
});
//add occurs on UI thread.
this.shippingDocuments.AddRange(tempDocuments);
}
Use a regular Timer and only dispatch the access to shippingDocuments.
As mentioned in comment, you can make use of Timers instead of DispatcherTimer. DispactherTimer will access the UIThread where as Timer use different thread from threadpool.
Also, you can dispatch an action to UIThread from different thread
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
//Do some UI stuffs
}));
Hope that helps.
Related
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?
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
Gist of it has probably been asked before, but I'm completely lost so I'm looking for some personal guidance. Been trying to make a stock tracker app for funsies using WinForms and the Yahoo API. Trying to get it so you can input a tracker symbol and it will make a new Label that will keep updating itself every so often. However, it keeps giving me error messages about "Cross-thread operation not valid". I've tried to do some googling, but yeah, completely lost. Here is most of the code, hope you guys can make some sense of it.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using YahooFinanceApi;
namespace stockpoging4
{
public partial class Form1 : Form
{
public Form1()
{
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
{
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
{
do_Things(result);
}
}
}
public async Task<string> getStockPrices(string symbol)
{
try
{
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
}
catch
{
return "404";
}
}
public async void do_Things(string result)
{
string price;
Label label = null;
if (label == null)
{
price = await getStockPrices(result);
label = new Label() { Name = result, Text = result + " $" + price };
flowLayoutPanel2.Controls.Add(label);
}
else
{
Thread testThread = new Thread(async delegate ()
{
uiLockingTask();
price = await getStockPrices(result);
label.Text = result + " $" + price;
label.Update();
});
}
System.Timers.Timer timer = new System.Timers.Timer(10000);
timer.Start();
timer.Elapsed += do_Things(results);
}
private void uiLockingTask() {
Thread.Sleep(5000);
}
}
}
Let me point out several things in your implementation.
You subscribe to timer.Elapsed after timer.Start that might be invalid in case of a short-timer interval
The event handler is called in background that's why you continuously get "Cross-thread operation not valid". UI components should be dispatched correctly from background threads, for example, by calling flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label))); and label.BeginInvoke(new Action(label.Update)). This change already would fix your exception.
Despite the fact that I would implement this functionality in a different way, here I post slightly changed code that just does exactly what you need with some tweaks.
public partial class Form1 : Form
{
Task _runningTask;
CancellationTokenSource _cancellationToken;
public Form1()
{
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
{
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
{
do_Things(result);
_cancellationToken = new CancellationTokenSource();
_runningTask = StartTimer(() => do_Things(result), _cancellationToken);
}
}
}
private void onCancelClick()
{
_cancellationToken.Cancel();
}
public async Task<string> getStockPrices(string symbol)
{
try
{
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
}
catch
{
return "404";
}
}
private async Task StartTimer(Action action, CancellationTokenSource cancellationTokenSource)
{
try
{
while (!cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(1000, cancellationTokenSource.Token);
action();
}
}
catch (OperationCanceledException) { }
}
public async void do_Things(string result)
{
var price = await getStockPrices(result);
var label = new Label() { Name = result, Text = result + " $" + price };
flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label)));
}
}
A much easier way is using async these days.
Here is a class which triggers an Action every interval:
public class UITimer : IDisposable
{
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
// use a private function which returns a task
private async Task Innerloop(TimeSpan interval, Action<UITimer> action)
{
try
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(interval, _cancellationTokenSource.Token);
action(this);
}
}
catch (OperationCanceledException) { }
}
// the constructor calls the private StartTimer, (the first part will run synchroniously, until the away delay)
public UITimer(TimeSpan interval, Action<UITimer> action) =>
_ = Innerloop(interval, action);
// make sure the while loop will stop.
public void Dispose() =>
_cancellationTokenSource?.Cancel();
}
If you work with dotnet 3.0 or higher, you can use the IAsyncDisposable. With this you're able to await the DisposeAsync method, so you can await the _timerTask to be finished.
And I created a new form with this as code behind:
public partial class Form1 : Form
{
private readonly UITimer _uiTimer;
private int _counter;
public Form1()
{
InitializeComponent();
// setup the time and pass the callback action
_uiTimer = new UITimer(TimeSpan.FromSeconds(1), Update);
}
// the orgin timer is passed as parameter.
private void Update(UITimer timer)
{
// do your thing on the UI thread.
_counter++;
label1.Text= _counter.ToString();
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
// make sure the time (whileloop) is stopped.
_uiTimer.Dispose();
}
}
The advantage is, that the callback runs on the UI thread but doesn't block it. The await Task.Delay(..) is using a Timer in the background, but posts the rest of the method/statemachine on the UI thread (because the UI thread has a SynchronizaionContext)
Easy but does the trick ;-)
I have a global variable called:
string tweet;
I run several background workers, that does nothing but wait on value change of the tweet variable. Then run a function called: ProcessTweet( object sender, MyCustomEventArgs args )
My question is what is the best way to handle the property changed event from all those background workers, and later process the results based on the tweet value and another argument passed to the ProcessTweet function.
I tried to take a look at INotifyPropertyChanged but I am not sure how to handle OnValueChange event from each background worker. Will it run the same ProcessTweet function once or each background worker will run an instance of that function?
EDIT:
private ITweet _LastTweet;
public ITweet LastTweet
{
get { return this._LastTweet; }
set
{
this._LastTweet = value;
}
}
Still not sure how to handle property change event the best way ^
And below is the rest of the code
private void bgworker_DoWork(object sender, DoWorkEventArgs e)
{
MyCustomClass myCustomClass = e.Argument as MyCustomClass;
//here I want to listen on the LastTweet Value Change event and handle it
}
List<BackgroundWorker> listOfBGWorkers = new List<BackgroundWorker>();
private BackgroundWorker CreateBackgroundWorker()
{
BackgroundWorker bgworker = new BackgroundWorker();
//add the DoWork etc..
bgworker.DoWork += new System.ComponentModel.DoWorkEventHandler(bgworker_DoWork);
return bgworker;
}
private void buttonStart_Click(object sender, EventArgs e)
{
for (int i = 0; i < 10; i++)
{
//Create the background workers
var bgworker = CreateBackgroundWorker();
listOfBGWorkers.Add(bgworker);
//get the MYCustomClass value;
var myCustomClass = SomeFunction();
bgworker.RunWorkerAsync(myCustomClass);
}
}
Ok - here's a small console app that demonstrates what I think you're trying to do.
It creates a 'source of tweets' in a thread.
You can subscribe to this 'source' and be notified when a new tweet 'arrives'.
You create TweetHandlers which have internal queues of tweets to process
You subscribe these TweetHandlers to the source
When a new tweet arrives, it is added to the queues of all the TweetHandlers by the event subscription
The TweetHandlers are set to run in their own Tasks. Each TweetHandler has its own delegate for performing a customizable action on a Tweet.
The code is as follows:
interface ITweet
{
object someData { get; }
}
class Tweet : ITweet
{
public object someData { get; set; }
}
class TweetSource
{
public event Action<ITweet> NewTweetEvent = delegate { };
private Task tweetSourceTask;
public void Start()
{
tweetSourceTask = new TaskFactory().StartNew(createTweetsForever);
}
private void createTweetsForever()
{
while (true)
{
Thread.Sleep(1000);
var tweet = new Tweet{ someData = Guid.NewGuid().ToString() };
NewTweetEvent(tweet);
}
}
}
class TweetHandler
{
public TweetHandler(Action<ITweet> handleTweet)
{
HandleTweet = handleTweet;
}
public void AddTweetToQueue(ITweet tweet)
{
queueOfTweets.Add(tweet);
}
public void HandleTweets(CancellationToken token)
{
ITweet item;
while (queueOfTweets.TryTake(out item, -1, token))
{
HandleTweet(item);
}
}
private BlockingCollection<ITweet> queueOfTweets = new BlockingCollection<ITweet>();
private Action<ITweet> HandleTweet;
}
class Program
{
static void Main(string[] args)
{
var handler1 = new TweetHandler(TweetHandleMethod1);
var handler2 = new TweetHandler(TweetHandleMethod2);
var source = new TweetSource();
source.NewTweetEvent += handler1.AddTweetToQueue;
source.NewTweetEvent += handler2.AddTweetToQueue;
// start up the task threads (2 of them)!
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var taskFactory = new TaskFactory(token);
var task1 = taskFactory.StartNew(() => handler1.HandleTweets(token));
var task2 = taskFactory.StartNew(() => handler2.HandleTweets(token));
// fire up the source
source.Start();
Thread.Sleep(10000);
tokenSource.Cancel();
}
static void TweetHandleMethod1(ITweet tweet)
{
Console.WriteLine("Did action 1 on tweet {0}", tweet.someData);
}
static void TweetHandleMethod2(ITweet tweet)
{
Console.WriteLine("Did action 2 on tweet {0}", tweet.someData);
}
}
The output looks like this:
Did action 2 on tweet 892dd6c1-392c-4dad-8708-ca8c6e180907
Did action 1 on tweet 892dd6c1-392c-4dad-8708-ca8c6e180907
Did action 2 on tweet 8bf97417-5511-4301-86db-3ff561d53f49
Did action 1 on tweet 8bf97417-5511-4301-86db-3ff561d53f49
Did action 2 on tweet 9c902b1f-cfab-4839-8bb0-cc21dfa301d5
I don't like this code but I always get confused with threads so wanted someone else's input before I suggest a change; Is this thread safe (Psuedo code though based on C#):
class ThreadCreator
{
private AnObject obj = new AnObject();
public ThreadCreator()
{
for (int i = 0; i < 100; ++i)
{
ThingToThread th = new ThingToThread();//don't care about losing ref to th for this question
th.sendMsg = this.getMessage;
Thread t = new Thread(th.doThing);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
}
public void getMessage( string stuff )
{
...
obj.DoThing(stuff);
...
}
}
class ThingToThread
{
public delegate void sendMsg(string stuff);
public void doThing()
{
...
this.sendMsg("ohh that's interesting");
...
}
}
You aren't calling back to the any other thread.
Your code will execute the delegate on the new thread, just like any other function call.
If getMessage is not thread-safe, your code will break.
Your example isn't thread safe, if you wanted thread-safety I would very much advise using a BackgroundWorker for executing in a new thread, but posting messages back to the main thread.
For example:
class ThreadCreator
{
private AnObject obj = new AnObject();
public ThreadCreator()
{
for (int i = 0; i < 100; ++i)
{
ThingToThread th = new ThingToThread();
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += th.DoWork;
worker.ProgressChanged += WorkerProgressChanged;
worker.RunWorkerAsync();
}
}
private void WorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
string stuff = e.UserState as string;
obj.DoThing(stuff);
}
}
And your ThingToThread DoWork method would look like:
public void DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
worker.ReportProgress(50, "Half way there");
worker.ReportProgress(100, "Finished");
}
I have this:
public class ServiceLibrary
{
public object result = null;
private bool finished = false;
public void testar()
{
ServiceReference.Service1SoapClient serviceReference = new ServiceReference.Service1SoapClient();
serviceReference.updateUserCompleted += new EventHandler<ServiceReference.updateUserCompletedEventArgs>(serviceReference_updateUserCompleted);
serviceReference.updateUserAsync();
ManualResetEvent m = new ManualResetEvent(true);
}
void serviceReference_updateUserCompleted(object sender, ServiceReference.updateUserCompletedEventArgs e)
{
result = e.Result;
finished = true;
}
}
and outside I have this:
public Home()
{
InitializeComponent();
ServiceLibrary serviceLibrary = new ServiceLibrary();
serviceLibrary.testar();
lblCharName.Text = Convert.ToString(serviceLibrary.result);
}
What should I do to the thread wait, so when I assign the text, it contains the value, please?
Thank you
Could you not use your ManualResetEvent? or create a fresh one.
I believe the ManualResetEvent is thread safe....
public class ServiceLibrary
{
public object result = null;
private bool finished = false;
public ManualResetEvent m;
public void testar()
{
ServiceReference.Service1SoapClient serviceReference = new ServiceReference.Service1SoapClient();
serviceReference.updateUserCompleted += new EventHandler<ServiceReference.updateUserCompletedEventArgs>(serviceReference_updateUserCompleted);
serviceReference.updateUserAsync();
m = new ManualResetEvent(false);
}
void serviceReference_updateUserCompleted(object sender, ServiceReference.updateUserCompletedEventArgs e)
{
result = e.Result;
finished = true;
m.Set();
}
}
public Home()
{
InitializeComponent();
ServiceLibrary serviceLibrary = new ServiceLibrary();
serviceLibrary.testar();
serviceLibrary.m.WaitOne();
lblCharName.Text = Convert.ToString(serviceLibrary.result);
}
Make your ManualResetEvent variable m a member variable of the class.
and inside your threaded method: serviceReference_updateUserCompleted, make sure to call
m.WaitOne();
What about
public class ServiceLibrary
{
public object result = null;
public void testar()
{
var serviceReference = new ServiceReference.Service1SoapClient();
using(var m = new ManualResetEvent(false))
{
Action<object, ServiceReference.updateUserCompletedEventArgs> handler =
(sender, e) =>
{
result = e.Result;
m.Set();
};
serviceReference.updateUserCompleted += handler;
serviceReference.updateUserAsync();
m.WaitOne();
serviceReference.updateUserCompleted -= handler;
}
}
}
I have a similar situation. I use a technique called polling, which is exactly what it sounds like. It may or may not be appropriate for you depending on your situation.
public class ServiceLibrary
{
public object result = null;
private bool finished = false;
public void testar()
{
ServiceReference.Service1SoapClient serviceReference = new ServiceReference.Service1SoapClient();
serviceReference.updateUserCompleted += new EventHandler<ServiceReference.updateUserCompletedEventArgs>(serviceReference_updateUserCompleted);
serviceReference.updateUserAsync();
ManualResetEvent m = new ManualResetEvent(true);
while !finished
Thread.Sleep(100);
doStuffWithResult(result);
}
void serviceReference_updateUserCompleted(object sender, ServiceReference.updateUserCompletedEventArgs e)
{
result = e.Result;
finished = true;
}