C# winform backgroundworker - c#

I am currently working on a home project for myself.
The program is written in C# using winforms.
The problem I'm currently experiencing is as followed:
I have a listview in my mainform called lvwGames
When I run the program without debugging, it runs fine.
However when I start with a debug, I get an error. This has something to do with the background worker thread.
Allow me to post some code to assist me.
private void MainViewLoad(object sender, EventArgs e)
{
RefreshGamesListView();
}
Nothing special here.
The reason I am calling RefreshGamesListView() is because I have to refresh on several occasions.
The method being called looks like this.
public void RefreshGamesListView()
{
pbRefreshGamesList.Value = 0;
bgwRefreshList.RunWorkerAsync();
}
So when the method is called, the background worker is called and runs the dowork method.
This one is quite big.
private void BgwRefreshListDoWork(object sender, DoWorkEventArgs e)
{
List<Game> games = _mainController.RetrieveAllGames();
int count = 1;
foreach (Game game in games)
{
string id = game.id.ToString();
var li = new ListViewItem(id, 0);
li.SubItems.Add(game.title);
li.SubItems.Add(game.Genre.name);
li.SubItems.Add(game.Publisher.name);
li.SubItems.Add(game.Platform.name);
li.SubItems.Add(game.CompletionType.name);
li.SubItems.Add(game.gameNotice);
lvwGames.Items.Add(li);
double dIndex = (double)(count);
double dTotal = (double)games.Count;
double dProgressPercentage = (dIndex / dTotal);
int iProgressPercentage = (int)(dProgressPercentage * 100);
count++;
bgwRefreshList.ReportProgress(iProgressPercentage);
}
}
When i run the code in debug, when the code is on lvwGames.Items.Add(li);
It gives me the following error:
Cross-thread operation not valid: Control 'lvwGames' accessed from a thread other than the thread it was created on.
I have absolutely no clue why.
I think it is code specific. But it can also mean I don't get the background worker completely, and specifically when to use it properly.
The reason I'm using it is because I'm loading a large large list from the database, I want to keep responsiveness in the UI when the list is loaded, and inform the users how far it is, using a progress bar.
If any code is missing, or you actually understand why this is happening PLEASE explain me why in this case its causing the error. You don't need to fix it for me. I just want to know WHY it's caused.
Thanks for taking the time to read this post. I hope to be able to continue using the debugger soon. :)

You need to call Conrol.Invoke when accessing visual controls from background threads.
if (_lvwGames.IsHandleCreated) {
Action addGameToList = () => {
string id = game.id.ToString();
var li = new ListViewItem(id, 0);
li.SubItems.Add(game.title);
....
_lvwGames.Items.Add(li);
};
if (_lvwGames.InvokeRequired) {
_lvwGames.Invoke(addGameToList);
} else {
addGameToList();
}
}
From Manipulating Controls from Threads
...For example, you might call a method that disables a button or
updates a display on a form in response to action taken by a thread.
The .NET Framework provides methods that are safe to call from any
thread for invoking methods that interact with controls owned by other
threads. The Control.Invoke method allows for the synchronous
execution of methods on controls...

This is because you're attempting to access a UI control (lvwGames) from a background thread. The way to make it work requires you to marshal the information back to the main UI thread and update the control from there:
private void BgwRefreshListDoWork(object sender, DoWorkEventArgs e)
{
List<Game> games = _mainController.RetrieveAllGames();
int count = 1;
foreach (Game game in games)
{
string id = game.id.ToString();
var li = new ListViewItem(id, 0);
li.SubItems.Add(game.title);
li.SubItems.Add(game.Genre.name);
li.SubItems.Add(game.Publisher.name);
li.SubItems.Add(game.Platform.name);
li.SubItems.Add(game.CompletionType.name);
li.SubItems.Add(game.gameNotice);
// This is the new line you need:
lvwGames.Invoke(new MethodInvoker(delegate { lvwGames.Items.Add(item) }));
double dIndex = (double)(count);
double dTotal = (double)games.Count;
double dProgressPercentage = (dIndex / dTotal);
int iProgressPercentage = (int)(dProgressPercentage * 100);
count++;
bgwRefreshList.ReportProgress(iProgressPercentage);
}
}
Normally you would check the InvokeRequired property first as mentioned in other answers, but there is really no need if you are always calling it from the background thread. Your DoWork method will always require an invoke call, so you might as well just go ahead and write it like that.

This happening cause, just like compiler cliams, you are going to update UI control content from another thread. You can not do that, as UI control can be updated only within main thread.
Please have look on this SO answer with example code provided:
Invoke from another thread

The background worker is not working properly if you run in debug mode in studio. If you have calls that use the windows handle to retrieve messages, then they will fail. If you for instance have a progressChanged event handler and this changes a text in a textbox that might fail.
I had this scenario: A Form that has a background worker. If I just start the worker without getting a dialog box up first then it works ok. If I show a dialog and then start the background worker then it fails. When I run the program normally it does not fail. It is somehow the debug environment that destroys the link between the events and the foreground window. I have changed my code to use invoke, and now all works both in when running in release and when I debug.
Here is a link explaining what can be done to make a program thread safe.
http://msdn.microsoft.com/en-us/library/ms171728(VS.80).aspx
I did not do the same as the sample to microsoft. I made delegates, assigned to the functions I needed to run. and called invoke on them.
sample pseudo code:
class MyClassWithDelegates
{
public delegate void ProgressDelegate( int progress );
public ProgressDelegate myProgress;
public void MyProgress(int progress)
{
myTextbox.Text = ..... ; // this is code that must be run in the GUI thread.
}
public MyClassWithDelegates()
{
myProgress = new ProgressDelegate(MyProgress);
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Invoke( myProgress, e.ProgressPercentage );
}
}
All code that potentially have to be run in the GUI thread of the application must be Invoked to be safe.

Related

Specific Use Case For Background Updating Progress Bar

I have been searching for over two days for a solution to this issue, and have finally decided to ask this question. I have found MANY relevant topics, but none of them seem to solve my problem. Most recently, I tried all of the solutions listed here.
Background Info: I have a class that handles traversing a massive amount of data. The class is called Traverse. There is a class method called DoFullTraverse (Traverse.DoFullTraverse), that runs a complete traverse than can (depending on user input) take up to 30 seconds. I am working in WPF, MVVM pattern. I would like to update a status bar on the gui for the progress of the DoFullTraverse. I calculate at the beginning of the function the exact number of loops required for calculation, and then increment a loop counter. Each time it reaches another 1/100, I increment the progress bar by 1. My progress bar (in xaml) has its value bound to a property in my VM called PBarV.
Most Recent Attempt: I have tried 100 different solutions but my most recent attempt looks like this:
private void runTraverseAndUpdateBar()
{
var worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_Complete);
worker.RunWorkerAsync();
while (!ThreadCheck)
{
Thread.Sleep(500);
}
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
for (int i = 0; i < 36; i++)
{
Thread.Sleep(500);
PBarV += 3;
}
e.Result = true;
}
void worker_Complete(object sender, RunWorkerCompletedEventArgs e)
{
ThreadCheck = true;
}
I believe that I am fundamentally misunderstanding how the background worker does work.
The Main Problem: I can get this method to work just fine, if I throw the function into the background worker and continue as usual. The problem is, I need the data from that function before my program to continue. Therefore, I need it to execute linearly but still update the status bar properly.
If anyone can shed some light on what I am missing or even nudge me in the right direction, I would appreciate it greatly.
Edit: This is not duplicate. The post you provided does not cover the issue of linear executing and waiting for the background worker to complete before continuing.
Edit 2: (As Per #Clemens Request)
I need the background worker to complete work before the main program continues. I am running the computationally heavy process in the background worker specifically so that the progress bar can be updated. But, BEFORE the main program can continue, I need the information from Traverse.DoFullTraverse();
To be VERY specific. The main program should halt all execution (other than updating status bar) until the background worker has completed Traverse.DoFullTraverse();
Here's a trivial example you can play around with and apply to your view model. It's important to use prototypes to create code and learn how it works in order to apply it to a larger and more complex application.
Please note that the example doesn't include trivial stuff like how to implement INotifyPropertyChanged and ICommand--those are easy to do.
Also, note the comments within TraverseYo. Specifically, the ones that tell you what thread you're currently on. Understanding the flow of execution across threads is important to get this working correctly. If you don't know what thread you're on, simply get the ApartmentState of the current thread. If it's STA, you're most likely on the UI thread.
public class LongLastingWorkViewModel : INotifyPropertyChanged
{
public bool Busy
{
// INotifyPropertyChanged property implementation omitted
}
public double PercentComplete
{
// INotifyPropertyChanged property implementation omitted
}
public ICommand PerformWork { get; set; }
public LongLastingWorkViewModel()
{
// delegated ICommand implementation omitted--there's TONS of it out there
PerformWork = new DelegatedCommand(TraverseYo);
}
private void TraverseYo()
{
// we are on the UI thread here
Busy = true;
PercentComplete = 0;
Task.Run(() => {
// we are on a background thread here
// this is an example of long lasting work
for(int i = 0; i < 10; i++)
{
Thread.Sleep(10 * 1000); // each step takes 10 seconds
// even though we are on a background thread, bindings
// automatically marshal property updates to the UI thread
// this is NOT TRUE for INotifyCollectionChanged updates!
PercentDone += .1;
}
Busy = false;
});
}
You can bind Busy to an overlay that blocks all UI while execution runs, bind PercentComplete to a progress bar, and PerformWork to a button.

thread call fires inconsistently

I am a beginner in high level programming languages. I am trying to make an WForms app for a serial port , im using VS 2010 C#
I get the following error:
Cross-thread operation not valid: Control 'rtxtDataArea' accessed from a thread other than the thread it was created on.
This happens here:
private void ComPort_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
recievedData = ComPort.ReadExisting(); //read all available data in the receiving buffer.
// Show in the terminal window
rtxtDataArea.ForeColor = Color.Green; // error ,
rtxtDataArea.AppendText(recievedData + "\n");
}
I have tried to change the color of a textbox when I receive some data.
It fires that cross thread error.
The question is why it does not fire the same error here, when I try to change the color of a label?
private void btnConnect_Click(object sender, EventArgs e)
{
if (ComPort.IsOpen)
{
disconnect();
}
else
{
connect();
rdText.ForeColor = Color.Blue;//ok, it works
}
}
; this works ; the first does not.
Why? Is not the ComPort_DataReceived_1 the same nature as btnConnect_Click ?
Or what is the reason?
I have read a lot about threads, but I understood nothing I can use, Can someone give an intuitive explanation ?
In winforms there is only one thread that may change anything on the UI like enable buttons, change text boxes, etc. Usually this is the UI thread. Quite often this is the only thread you have.
However, if you start a new thread, this thread might want to change the UI. This happens especially if this new thread fires an event that is received by your form.
Whenever you see the message accessed from a thread other than the thread it was created on, you can be almost certain this is the case.
The most easy solution to solve this is using the functions Control.IsInvokeRequired and Control.Invoke. The pattern to do this is as follows. The following function updates myButton on myForm
private void UpdateMyButton (MyType myParameter)
{
if (myButton.InvokeRequired)
{ // this is not the thread that created the button to update
this.Invoke(new MethodInvoker( () => this.UpdateMyButton(myParameter)));
// this will let the UI thread call this function with the same parameter.
}
else
{ // Now called by the UI thread: this thread may change myButton
myButton.Enabled = myParameter.ShouldButtonEnable;
myButton.Text = myParameter.ButtonText;
}
}
By the way, if you have to update several controls on your form you ought to check InvokeRequired for each of these controls. However, since they are usually created by the same UI thread it is sufficient to check for this.InvokeRequired.
Control.Invoke returns after the invoke is completed, so after all items are updated. Upon return of Invoke you can use the result of UpdateMyButton.
If you don't want your non-ui thread to wait for completion of UpdateMyButton, consider the use of Control.BeginInvoke: "hey UI thread, whenever you've got time, can you UpdateMyButton for me. Of course in that case you can't use the results of UpdateMyButton
Because "DataReceived" runs on another thread and not UI thread. You must use Invoke for that :
private void ComPort_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
recievedData = ComPort.ReadExisting(); //read all available data in the receiving buffer.
if (InvokeRequired)
{
// If not on UI thread, then invoke
Invoke(new MethodInvoker(() =>
{
// Show in the terminal window
rtxtDataArea.ForeColor = Color.Green; // error ,
rtxtDataArea.AppendText(recievedData + "\n");
}));
}
else
{
// On UI thread, invoke not needed
// Show in the terminal window
rtxtDataArea.ForeColor = Color.Green; // error ,
rtxtDataArea.AppendText(recievedData + "\n");
}
}

C# Trouble Using Safe Thead or Background Worker

Fairly frustrating since this seems to be well documented and the fact that I accomplished this before, but can't duplicate the same success. Sorry, I'll try to relate it all clearly.
Visual Studio, C# Form, One Main Form has text fields, among other widgets.
At one point we have the concept that we are "running" and therefore gathering data.
For the moment, I started a one second timer so that I can update simulated data into some fields. Eventually that one second timer will take the more rapid data and update it only once per second to the screen, that's the request for the application right now we update at the rate we receive which is a little over 70 Hz, they don't want it that way. In addition some other statistics will be computed and those should be the field updates. Therefore being simple I'm trying to just generate random data and update those fields at the 1 Hz rate. And then expand from that point.
Definition and management of the timer: (this is all within the same class MainScreen)
System.Timers.Timer oneSecondTimer;
public UInt32 run_time = 0;
public int motion = 5;
private void InitializeTimers()
{
this.oneSecondTimer = new System.Timers.Timer(1000);
this.oneSecondTimer.Elapsed += new System.Timers.ElapsedEventHandler(oneSecondTimer_elapsed);
}
public void start_one_second_timer()
{
run_time = 0;
oneSecondTimer.Enabled = true;
}
public void stop_one_second_timer()
{
oneSecondTimer.Enabled = false;
run_time = 0;
}
Random mot = new Random();
private void oneSecondTimer_elapsed(object source, System.Timers.ElapsedEventArgs e)
{
run_time++;
motion = mot.Next(1, 10);
this.oneSecondThread = new Thread(new ThreadStart(this.UpdateTextFields));
this.oneSecondThread.Start();
}
private void UpdateTextFields()
{
this.motionDisplay.Text = this.motion.ToString();
}
motionDisplay is just a textbox in my main form. I get the Invalid Operation Exception pointing me towards the help on how to make Thread-Safe calls. I also tried backgroundworker and end up with the same result. The details are that motionDisplay is accessed from a thread other than the thread it was created on.
So looking for some suggestions as to where my mistakes are.
Best Regards. I continue to iterate on this and will update if I find a solution.
Use a System.Forms.Timer rather than a System.Timers.Timer. It will fire it's elapsed event in the UI thread.
Don't create a new thread to update the UI; just do the update in the elapsed event handler.
Try this
private void UpdateTextFields()
{
this.BeginInvoke(new EventHandler((s,e)=>{
this.motionDisplay.Text = this.motion.ToString();
}));
}
This will properly marshall a call back to the main thread.
The thing with WinForm development is that all the controls are not thread safe. Even getting a property such as .Text from another thread can cause these type of errors to happen. To make it even more frustrating is that sometimes it will work at runtime and you won't get an exception, other times you will.
This is how I do it:
private delegate void UpdateMotionDisplayCallback(string text);
private void UpdateMotionDisplay(string text) {
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.motionDisplay.InvokeRequired) {
UpdateMotionDisplayCallback d = new UpdateMotionDisplayCallback(UpdateMotionDisplay);
this.Invoke(d, new object[] { text });
} else {
this.motionDisplay.Text = text;
}
}
When you want to update the text in motionDisplay just call:
UpdateMotionDisplay(this.motion.ToString())

Updating progressbar external class

Basically, I have a class and inside it a function which counts all lines within a text file.
I need it to update the progress bar from Form1 with each line it counts. I have tried:
public static void rfile(string f)
{
string[] lines = File.ReadAllLines(f);
Form1 form = new Form1();
foreach (string l in lines)
{
form.increaseProg();
}
}
Form.cs
public void increaseProg()
{
progressBar.Value++;
System.Threading.Thread.Sleep(1000);
progressBar.Refresh();
}
But that doesn't seem to increase the progress bar at all.
You can leverage the Progress class to make updating the UI during a long running operation easy on everyone involved. Create the Progress class within your form, and indicate how it should update the UI when it is given progress. Then give that object to the other class that is going to be doing the long running work:
private void button1_Click(object sender, EventArgs args)
{
Progress<int> progress = new Progress<int>();
progress.ProgressChanged += (p, value) => progressbar1.Value = value;
Task.Run(() => SomeOtherClass.DoWork("c:/temp.txt", progress));
}
The long running work is of course done in another thread to avoid blocking the UI. The Progress class will take care of marshaling the ProgressChanged event to the UI thread for us, so we don't need to think about it.
Now for the worker we just need to report progress when needed:
public class SomeOtherClass
{
public static void DoWork(string filepath, IProgress<int> progress)
{
int currentProgress = 0;
foreach (var line in File.ReadLines(filepath))
{
DoSomethingWithLine();
currentProgress++;
progress.Report(currentProgress);
}
}
}
Note that another advantage of this approach is that SomeOtherClass doesn't need to know anything about the form. It can be called by anyone that can provide an IProgress object. If you have some other form needing to call that method you don't need to change it at all. It also means that if one developer is writing the form and another is coding the long running process they only need to agree on the signature of the DoWork method; and from then on the UI guy and do all of the UI work and the non-UI guy can do all of the non-UI work, and they don't need to worry about what the other person is doing.
As for why your code isn't working, the problem is that your worker method isn't accessing the instance of the form that is being displayed, you're creating a brand new form, modifying it's progress bar, never showing it to anyone, and then throwing it away.
This is because you are reading the lines and when updating the progressbar, the UI won't entirely refresh as you are imagining. What you need to do, as it sometimes can work, is to do a progressBar.Refresh() (I cannot remember if the progressbar has a refresh function) so it can update the form visuals (specifically the progressbar) whilst it is working the way through the lines. basically, its "too fast" which can cause the delay in updating.
The progress bar is updating, you just don't see the refresh happening. One way is to make it multithreaded perhaps but this would be overkill for reading lines and updating the UI.
not only this, your code basically does NOT show the UI... it is updating, and not showing the UI, you need to Show() the form and perform the updates on it. you instantiate the form, but you don't actually show it.
using System.Windows.Threading;
Dispatcher.CurrentDispatcher.Invoke(new System.Action(() => ProgressBar1.Value = value));
You can sometimes get away with using the line above to update the UI while doing other work. you'll need some way to keep track of the progress numerically though.
as stated, since you are tying up your main thread with processing, you can't update the UI on the same thread. The above carves out a tiny chunk of time to do this. The only other method would be a background worker, though that is probably overkill for your situation.
edit (Display on separate form)
Did you set the modifier on your progressbar to public? This solution works just fine for showing progress in a separate window, and it updates correctly.
private void button1_Click(object sender, EventArgs e)
{
Form2 f2 = new Form2();
f2.progressBar1.Maximum = 1000;
f2.Show();
for(int i = 0; i < 1000; i++)
{
Thread.Sleep(10);
f2.progressBar1.Value++;
}
}
Note that the UI of the progress window will be locked until the underlying process completes. I just tested this and it works fine.
Update progress on same form, but from different class
You need to pass a reference of the progressbar so your method knows what it is updating.
}
private void button1_Click(object sender, EventArgs e)
{
Class1 c = new Class1();
progressBar1.Maximum = 1000;
for(int i = 0; i < 1000; i++)
{
Thread.Sleep(10);
c.IncreaseProgress(progressBar1);
}
}
}
class Class1
{
public void IncreaseProgress(ProgressBar p)
{
p.Value++;
}
}
Both of the above methods will lock the UI while updating though. Use a BackgroundWorker to avoid this. This solution works well in quick and dirty utilities though.

MultiThreading inside Loop and Wait Threads WinForms and C#

I'm in trouble with this code. I'm using .Net (C#) with Winform Application.
I need to run RunProgram Method which has loop that make a call to a method named ListLoop.
In this function there is a forach that creates 1 thread for each element a list.
(Please Read the code before continue to read the description so you could understand what i'm talking about )
The problem is that if i dont make any control in the "for" (RunProgram Method) it starts (of course) 10 times The ListLoop Function.
So i would add in that "For" a code which wait that all Threads are terminated, so i can do Something and Then continue with the next loop.
I tried thread.join() but it freeze my UI application
(it's Application which inside has a WebControl Browser).
Even if i Try to play with returnThred and with thread.isAlive it still freezes UI.
If i hadn't the Multithread i'll dont stay here with those problems but it's the only good solution for my program i think.
Is there a simple solution for my code?
Update: Maybe it'snt clear my question.
I just want run the ListLoop X times but before start the next one i want wait that all threads are dead (That ones of the first call) so i can do some control and continue with the loop inside RunProgram.
Update2 I have this UI application which has a WebBrowser Control. I have a List of Links Object (each element of this class has string url and idHost =1 2 3 4...1 for google 2 for yahoo etc...)
I want make a loop where my program start a newTab (with Method AddTab(url) ) for each element of the list. When all links are opened (and so all the threads are deads and) i need to do something that count how many pages opened and who was the idHost save it and start another Loop with the list(This list take random element from a Bigger List)
Update 3 I just tried with BackGround Worker but i cant use it cause the WebKit that i'm using give COM error. Something for the Tasks.
Thanks
private void RunProgram()
{
List<Links> TheList = new List<Links>();
//Do something to Populate the List
List<System.Threading.Thread> returnThread = new List<.....>();
for(int i=0; i<10; i++)
{
returnThread=ListLoop(TheList);
// ???????????
// When Loop Method has finished and all threads stopped
// Do something
// Continue for the next Loop
}
}
private List<System.Threading.Thread> ListLoop(List<Links> list)
{
List<System.Threading.Thread> threading = new List<System.Threading.Thread>();
foreach (Links link in list)
{
Links tmp = new Links();
tmp = link;
var thread = new System.Threading.Thread(p =>
{
lock (l)
{
Action action = () =>
{
AddTab(tmp);
};
this.Invoke(action);
if (tmp.idHost == 1) //if IDhost == Google wait 5sec
{
System.Threading.Thread.Sleep(5000);
}
else
{
System.Threading.Thread.Sleep(2000);
}
}
});
threading.Add(thread);
thread.Start();
}
return threading;
}
If RunProgram is called from your main application, it will freeze your main form if it sleeps or waits for threads to terminate. You should run the RunProgram method in its own thread, so it can then create the worker threads, and then you can wait for the threads to complete in your for loop before starting new ones.
You could use AutoResetEvent to signal when threads are finished so you can simply wait on the AutoResetEvent before continuing the loop. The BackgroundWorker class may be a good class to look at for creating your threads as well.
I'm not sure if I understood your question properly, but:
You work asynchronously here... You can't wait in your code without stopping the GUI.
I think your solution will be to separate your function into 2 parts - The 1st one you just written, and the second one works after the threads are all dead.
For the second part (check the threads) I'd use either another thread (That waits until the threads are dead than continues to your code) or a Timer if you'd like to save threads and integrate easily into the main form
UPDATE:
Here is an example of how a blocking function that doesn't block the GUI thread:
using System.Windows.Forms;
using System.Threading;
using System;
namespace Threads
{
public partial class Form1 : Form
{
public event EventHandler OnSomethingFinishes;
public Form1()
{
InitializeComponent();
OnSomethingFinishes += new EventHandler(Form1_OnSomethingFinishes);
}
void Form1_OnSomethingFinishes(object sender, EventArgs e)
{
this.Invoke(new EventHandler(Form1_OnSomethingFinishesSafe), sender, e);
}
void Form1_OnSomethingFinishesSafe(object sender, EventArgs e)
{
this.Text = "Done!";
}
private void BlockingFunction(object a_oParameter)
{
// Do something that blocks
Thread.Sleep(2000);
if (OnSomethingFinishes != null)
OnSomethingFinishes(this, null);
}
private void button1_Click(object sender, EventArgs e)
{
Thread l_oThread = new Thread(BlockingFunction);
l_oThread.Start();
this.Text = "Please Wait...";
}
}
}
button1 starts the process. Notice that you have to invoke the function after the event is handled to move the control to the main GUI thread
Hope this helps

Categories