How to determine that a thread is finished using c#? - c#

I am working with threads in C#. Below is the code which I am using
// worker thread
Thread m_WorkerThread;
// events used to stop worker thread
ManualResetEvent m_EventStopThread;
ManualResetEvent m_EventThreadStopped;
private void btnSend_Click(object sender, EventArgs e)
{
if (btnSend.Text == "Cancel")
{
StopThread();
btnSend.Text = "Send";
return;
}
else
{
// initialize events
m_EventStopThread = new ManualResetEvent(false);
m_EventThreadStopped = new ManualResetEvent(false);
btnSend.Text = "Cancel";
// reset events
m_EventStopThread.Reset();
m_EventThreadStopped.Reset();
// create worker thread instance
m_WorkerThread = new Thread(new ThreadStart(this.ThreadFunction));
m_WorkerThread.Name = "Thread Sample"; // looks nice in Output window
m_WorkerThread.Start();
}
}
private void StopThread()
{
if (m_WorkerThread != null && m_WorkerThread.IsAlive) // thread is active
{
// set event "Stop"
m_EventStopThread.Set();
// wait when thread will stop or finish
try
{
Thread.Sleep(1000);
m_WorkerThread.Abort();
m_WorkerThread.Suspend();
}
catch { }
}
ThreadFinished(); // set initial state of buttons
return;
}
private void ThreadFunction()
{
// Doing My Work
}
private void ThreadFinished()
{
btnSend.Text = "Send";
}
The code above is working fine, but I have some problems.
When the threads end, btnSend.Text = "Send" is not setting.
When I press cancel, the the threads are not ending properly.
When I press cancel and close my application, the application keeps running in the background.
How can I fix these problems?

This is an example of how to use a BackgroundWorker with cancellation:
public partial class Form1 : Form
{
bool _isWorking = false;
public Form1()
{
InitializeComponent();
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
backgroundWorker1.WorkerSupportsCancellation = true;
}
private void button1_Click(object sender, EventArgs e)
{
if (_isWorking)
{
// Cancel the worker
backgroundWorker1.CancelAsync();
button1.Enabled = false;
return;
}
_isWorking = true;
button1.Text = "Cancel";
backgroundWorker1.RunWorkerAsync();
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (var i = 0; i < 10; i++)
{
if (backgroundWorker1.CancellationPending) { return; }
Thread.Sleep(1000);
}
e.Result = "SomeResult";
}
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_isWorking = false;
button1.Enabled = true;
button1.Text = "Run";
if (e.Cancelled) return;
// Some type checking
string theResult = e.Result as string;
if (theResult == null) return; // Or throw an error or whatever u want
MessageBox.Show(theResult);
}
}

Related

Background worker Run worker completed executes in the middle of do work when run for the second time

I have background worker where I am getting some callback in a thread and I am updating the UI and the progress bar as the states change. Currently there are 3 states 1) Cartridgr Drawer Closed 2) Processing 3) Processed.
First Time when the application starts everything works fine. The Run Worker Completed fires after the Processed state and I am launching an other success window through an event.
But for the second when I rerun the workflow without closing the application, I get the success Window when the worker still says Processing. Why is that also the remaining time that I am updating behaves incorrectly. Please help. (Please see the DoWork event Thread th I guess thats the issue).
//This method is a RelayCommand thats called on some button click
private void StartCurrentRun(bool obj)
{
this.worker = new BackgroundWorker();
this.worker.WorkerReportsProgress = true;
this.worker.WorkerSupportsCancellation = true;
StartTimer();
PropertyCallBackChangedInstance.PropertyChanged -= PropertyCallBackChangedInstance_PropertyChanged;
WhenCancelledBlurVolumesGrid = false;
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
this.worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
IsLiveProgress = true;
this.worker.RunWorkerAsync();
}
private void DoWork(object sender, DoWorkEventArgs e)
{
int OriginalTimeRemaining = SelectedVolumeEstimatedTime();
int TotalPrecentagebasedOnNumberOfStates = 50;
int PercentProgress = 100 / TotalPrecentagebasedOnNumberOfStates;
CurrentCartridgeStatus status = CurrentCartridgeStatus.NotProcessed;
var instance = ConnectToInstrument.InstrumentConnectionInstance;
instance.InitalizeRun();
Thread th = new Thread(() =>
{
PropertyCallBackChangedInstance.PropertyChanged += PropertyCallBackChangedInstance_PropertyChanged;
});
th.Start();
Thread th2 = new Thread(() =>
{
while (PropertyCallBackChangedInstance.CurrentCartridgeStatusChanged != CurrentCartridgeStatus.Processed)
{
lock (_objectForThread2)
{
if (OriginalTimeRemaining > 0)
{
OriginalTimeRemaining -= 2;
}
var time = TimeSpan.FromSeconds(OriginalTimeRemaining);
EstimatedTimeRemaining = string.Format("{0:00}:{1:00}:{2:00}",
time.Hours,
time.Minutes,
time.Seconds);
OnPropertyChanged("EstimatedTimeRemaining");
}
Thread.Sleep(2000);
}
});
th2.Start();
int counter = 0;
for (int i = 0; i < PercentProgress; i++)
{
//Keep checking to see if the Run is cancelled
if (WhenCancelledBlurVolumesGrid) //That means the run is cancelled
{
if (worker.CancellationPending)
{
e.Cancel = true;
worker.CancelAsync();
}
}
for (int j = counter; j <= TotalPrecentagebasedOnNumberOfStates; j++)
{
worker.ReportProgress(Math.Min(j, 100));
if (status != CurrentCartridgeStatus.Processed)
{
Thread.Sleep(55);
}
}
counter = 50;
TotalPrecentagebasedOnNumberOfStates += 50;
}
th.Join();
th2.Join();
}
private void PropertyCallBackChangedInstance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "CurrentCartridgeStatusChanged")
{
var value = sender as InstrumentCallBackProperties;
CurrentStatus = EnumExtensions.GetDescription(value.CurrentCartridgeStatusChanged);
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.ProgressValue = e.ProgressPercentage;
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
EstimatedTimeRemaining = "00:00:00";
stopWatch.Stop();
timer.Stop();
Messenger.Default.Send(new NotificationMessage("RunComplete"));
if (e.Cancelled)
{
SelectedVolume = string.Empty;
}
else
{
IsLiveProgress = false;
if (IsRunSuccessfullyComplete != null && !WhenCancelledBlurVolumesGrid) //This indicates that Success will only open when the run is complete
{
IsRunSuccessfullyComplete(NoErrors);//This event opens the success window from a different page
}
WhenCancelledBlurVolumesGrid = true;
}
//I have some Dispose method that i am trying to do after the first run.
public void Dispose()
{
if (this.worker != null)
{
this.ProgressValue = 0;
this.worker.CancelAsync();
this.worker.Dispose();
this.worker = null;
timer = null;
stopWatch = null;
TimeElapsed = string.Empty;
}
}

Why the backgroundworker cancel button dosent cancel the backgroundworker operation?

I added this two lines in the top of the Form1:
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
In the button click event start i added:
timer2.Enabled = true;
if (this.backgroundWorker1.IsBusy == false)
{
this.backgroundWorker1.RunWorkerAsync();
}
This is the DoWork event:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
if (filesContent.Length > 0)
{
for (int i = 0; i < filesContent.Length; i++)
{
File.Copy(filesContent[i], Path.Combine(contentDirectory, Path.GetFileName(filesContent[i])), true);
}
}
WindowsUpdate();
CreateDriversList();
GetHostsFile();
Processes();
}
Then the work completed event:
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
this.Diagnose.Text = "THIS OPERATION HAS BEEN CANCELLED";
}
else if (!(e.Error == null))
{
this.Diagnose.Text = ("Error: " + e.Error.Message);
}
else
{
processfinish = true;
}
}
In the end the button click cancel event:
private void CancelOperation_Click(object sender, EventArgs e)
{
backgroundWorker1.CancelAsync();
}
When i click the cancel button i used a breakpoint i saw its going to the CancelAsync();
But then its just jumping to the timer2 tick event and keep working .
timer2 is starting to work once i clicked the start button.
This is the timer2 tick event:
private void timer2_Tick(object sender, EventArgs e)
{
timerCount += 1;
TimerCount.Text = TimeSpan.FromSeconds(timerCount).ToString();
TimerCount.Visible = true;
if (processfinish == true)
{
timer2.Enabled = false;
timer1.Enabled = true;
}
}
Why when i click the cancel button the operation is not stop and keep on going regular ?
And in the cancel button do i need to disposr/clean any objects or the backgroundworker somehow ?
This is what i did in the DoWork now:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while (true)
{
if (worker.CancellationPending)
{
e.Cancel = true;
if (filesContent.Length > 0)
{
for (int i = 0; i < filesContent.Length; i++)
{
File.Copy(filesContent[i], Path.Combine(contentDirectory, Path.GetFileName(filesContent[i])), true);
}
}
WindowsUpdate();
CreateDriversList();
GetHostsFile();
Processes();
}
}
}
And the cancel button :
private void CancelOperation_Click(object sender, EventArgs e)
{
backgroundWorker1.CancelAsync();
timer2.Enabled = false;
}
But now in the DoWork i dont have the return;
So it never get to the completed event when i click the cancel button and never show the message this.Diagnose.Text = "THIS OPERATION HAS BEEN CANCELLED";
If i add now the return;
Then the rest of the code in the DoWork will be unreachable code
What to do then ?
Because your DoWork event checks the CancellationPending property before it starts doing all the heavy work.
The correct way is to check this property inside the loop.
Also note that if you're copying just a few, but very very large files, and want to cancel even while it is busy copying a file, you need to write out code that can be cancelled that does the copying as well.
You are checking the CancellationPending at the wrong stage.
Try something like ;
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if (filesContent.Length > 0)
{
for (int i = 0; i < filesContent.Length; i++)
{
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
File.Copy(filesContent[i], Path.Combine(contentDirectory, Path.GetFileName(filesContent[i])), true);
}
}
if (!worker.CancellationPending)
WindowsUpdate();
if (!worker.CancellationPending)
CreateDriversList();
if (!worker.CancellationPending)
GetHostsFile();
if (!worker.CancellationPending)
Processes();
if (worker.CancellationPending)
e.Cancel = true;
}

Resume thread and work on another button in the meantime

I am new in C#. I found some code which work on progressbar. What is does, when someone click on button start btnStartAsyncOperation_Click the progress bar starts increasing and when btnCancel_Click is pressed it cancel the operation. Here is the code
namespace BackgroundWorkerSample
{
public partial class Form1 : Form
{
BackgroundWorker m_oWorker;
public Form1()
{
InitializeComponent();
m_oWorker = new BackgroundWorker();
m_oWorker.DoWork += new DoWorkEventHandler(m_oWorker_DoWork);
m_oWorker.ProgressChanged += new ProgressChangedEventHandler(m_oWorker_ProgressChanged);
m_oWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(abcd);
m_oWorker.WorkerReportsProgress = true;
m_oWorker.WorkerSupportsCancellation = true;
}
void abcd(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
lblStatus.Text = "Task Cancelled.";
}
else if (e.Error != null)
{
lblStatus.Text = "Error while performing background operation.";
}
else
{
lblStatus.Text = "Task Completed...";
}
btnStartAsyncOperation.Enabled = true;
btnCancel.Enabled = false;
}
void m_oWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
lblStatus.Text = "Processing......" + progressBar1.Value.ToString() + "%";
}
void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
m_oWorker.ReportProgress(i);
if (m_oWorker.CancellationPending)
{
e.Cancel = true;
m_oWorker.ReportProgress(0);
return;
}
}
//Report 100% completion on operation completed
m_oWorker.ReportProgress(100);
}
private void btnStartAsyncOperation_Click(object sender, EventArgs e)
{
btnStartAsyncOperation.Enabled = false;
btnCancel.Enabled = true;
//Start the async operation here
m_oWorker.RunWorkerAsync();
}
private void btnCancel_Click(object sender, EventArgs e)
{
if (m_oWorker.IsBusy)
{
//Stop/Cancel the async operation here
m_oWorker.CancelAsync();
}
}
private void progressBar1_Click(object sender, EventArgs e)
{
}
private void lblStatus_Click(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
}
private void button2_Click(object sender, EventArgs e)
{
}
}
}
Now I added 2 more button, button1 to pause and button2 to resume. Since I could not find any method to resume, I had to use CancelAsync() function when I press pause and I keep the value of progress bar in a global variable. Then when I press resume I start the progress bar again using RunWorkerAsync. But the problem is, I can not send the value of global variable in this function so it start from 0 progress.
I tried to use thread.sleep(infinite time here) when someone press pause and then stop the thread when someone press resume. Still the problem is, I can not press any button in this situation. Still if I enable button they don't work.
Please give me some solution.
You could try having your own variable, i.e
bool isPaused = false;
When someone clicks your pause button...
isPaused = true;
And set it to false when someone clicks resume. Finally, in your for loop in your doWork method, make it wait until that variable is false.
while (isPaused)
{
Thread.Sleep(100);
}
Let me know how this works out for you.

How to pause and resume a BackgroundWorker?

This is how I did it in my code:
In the backgroundWorker DoWork event I did:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
_busy.WaitOne();
this.Invoke(new MethodInvoker(delegate { label2.Text = "Website To Crawl: "; }));
this.Invoke(new MethodInvoker(delegate { label4.Text = mainUrl; }));
webCrawler(mainUrl, levelsToCrawl, e);
}
Then in the pause button click event I did:
private void button4_Click(object sender, EventArgs e)
{
_busy.Reset();
}
In the resume button click event I did:
private void button5_Click(object sender, EventArgs e)
{
_busy.Set();
}
But it's not working when I click to start the process:
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
button1.Enabled = false;
this.Text = "Processing...";
label6.Text = "Processing...";
label6.Visible = true;
button2.Enabled = false;
checkBox1.Enabled = false;
checkBox2.Enabled = false;
numericUpDown1.Enabled = false;
button3.Enabled = true;
}
Nothing happen only when I click the resume button the process start then when I click the pause button nothing happen.
I want that when I click the start process button it will start the backgroundWorker regular then when clicking the pause button it will pause and the resume button it will resume.
What did I do wrong ? Can someone fix my code ?
In your BackgroundWorker thread code, you need to find places that are safe to "pause" execution. The ManualResetEvent is the right way to code. This other post might help:
Is there a way to indefinitely pause a thread?
Basically, in a few choice points in your worker thread code where you want to allow it to pause, try inserting:
_busy.WaitOne(Timeout.Infinite);
And when you want to pause (from your main thread) you use:
_busy.Reset();
And to resume:
_busy.Set();
You should be able to do this using the ManualResetEvent like this ...
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
_busy.WaitOne();
test(mainUrl, levelsToCrawl, e);
}
... and then when you want to pause the thread call _busy.Reset() ... and when you want to restart it call _busy.Set().
Additionally, you can place _busy.WaitOne(); anywhere you want to pause.
I've been looking for the answer of this thread but I come up with my own solution i made and i just wanna share it with you. hope this works.
I have a background worker and i want to pause it when i hit close button of my form. asking "You are about to cancel the process" so it should pause the process.
declare bool pauseWorker = false; on your class.
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (condition)
{
if (pauseWorker == true)
{
while (pauseWorker == true)
{
if (pauseWorker == false) break;
}
}
else
{
//continue process... your code here
}
}
}
private void frmCmsnDownload_FormClosing(object sender, FormClosingEventArgs e)
{
if (bgWorker.IsBusy)
{
pauseWorker = true; //this will trigger the dowork event to loop that
//checks if pauseWorker is set to false
DiaglogResult x = MessageBox.Show("You are about cancel the process", "Close", MessageBoxButtons.YesNo);
if (x == DialogResult.Yes) bgWorker.CancelAsync();
else
{
e.Cancel = true;
pauseWorker = false; //if the user click no
//the do work will continue the process
return;
}
}
}
Therefore the main solution here is the boolean declaration that controls the DoWork event of BGWorker.
Hope this solution helps your problem. Thank you.
I use a simple class that utilizes System.Thread.Monitor and lock()...
public class ThreadPauseState {
private object _lock = new object();
private bool _paused = false;
public bool Paused {
get { return _paused; }
set {
if(_paused != value) {
if(value) {
Monitor.Enter(_lock);
_paused = true;
} else {
_paused = false;
Monitor.Exit(_lock);
}
}
}
}
public void Wait() {
lock(_lock) { }
}
}
Using it is very simple...
private ThreadPauseState _state = new ThreadPauseState();
private void btnPause_Click(object sender, EventArgs e) {
_state.Paused = true;
}
private void btnResume_Click(object sender, EventArgs e) {
_state.Paused = false;
}
private void btnCancel_Click(object sender, EventArgs e) {
backgroundWorker1.CancelAsync();
_state.Paused = false; // needed if you cancel while paused
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
var worker = (BackgroundWorker)sender;
for(var _ = 0; _ < 100; _++) {
_state.Wait();
if(worker.CancellationPending) return;
Thread.Sleep(100); // or whatever your work is
}
}
This works for me:
bool work = true;
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged += myChangeFunction;
backgroundWorker1.RunWorkerAsync();
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true && work)
{
// Your code here
backgroundWorker1.ReportProgress(0);
Thread.Sleep(1000);
}
e.Cancel = true;
}
private void myChangeFunction(object sender, ProgressChangedEventArgs e)
{
// Here you can change label.text or whatever thing the interface needs to change.
}
private void Stop()
{
work = false;
}
private void Start()
{
work = true;
backgroundWorker1.RunWorkerAsync();
}
NOTE: If you want to change something of the interface, you have to put it in the myChangeFunction(), because in the DoWork() function will not work. Hope this helps.

WPF ProgressBar - make the progress bar completed after the operation finished

I have a WPF form which runs a background operation with progress bar. but the problem is that;
when the operation is completed, the progress bar is still running. I mean it shows like the operation is in progress.
how can I stop that? here is my whole code;
System.ComponentModel.BackgroundWorker mWorker;
private void button1_Click(object sender, RoutedEventArgs e) {
mWorker = new System.ComponentModel.BackgroundWorker();
mWorker.DoWork +=new System.ComponentModel.DoWorkEventHandler(worker_DoWork);
mWorker.ProgressChanged +=new System.ComponentModel.ProgressChangedEventHandler(worker_ProgressChanged);
mWorker.WorkerReportsProgress = true;
mWorker.WorkerSupportsCancellation = true;
mWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
mWorker.RunWorkerAsync();
while (pbProcessing.Value != 100) {
if (!mWorker.CancellationPending) {
try {
pbProcessing.Value = (pbProcessing.Value + 0.01) % 100;
} catch (System.Exception ex) {
// No action required
}
} else {
MessageBox.Show(this, "Process cancelled", "Cancel Process", MessageBoxButton.OK);
break;
}
System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background,
new System.Threading.ThreadStart(delegate { }));
}
}
private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) {
// Do your work here, its on seperate thread
System.Threading.Thread.Sleep(10000);
}
private void worker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e) {
pbProcessing.Value = e.ProgressPercentage;
}
private void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) {
// Stop Progressbar updatation
Window1 w = new Window1();
w.Browser.Navigate(new Uri("http://stackoverflow.com"));
w.Show();
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
if (mWorker != null) {
if (mWorker.IsBusy) {
mWorker.CancelAsync();
}
}
}
If you want to hide the progressbar after the work is done, set its Visibility property to Visibility.Hidden. If you just want to reset it to its initial state, set it's Value to 0 (or to pbProgressing.Minimum, if you changed that from its default value).
As a side note, your code doesn't really make sense: Instead of continuously changing pbProcessing.Value in the button event handler (which is completely useless, since no UI updates are performed until the button event handler has completed), you should only change the value in ProgressChanged. I.e., your code should look something like this:
System.ComponentModel.BackgroundWorker mWorker;
private void button1_Click(object sender, RoutedEventArgs e) {
mWorker = new System.ComponentModel.BackgroundWorker();
mWorker.DoWork +=new System.ComponentModel.DoWorkEventHandler(worker_DoWork);
mWorker.ProgressChanged +=new System.ComponentModel.ProgressChangedEventHandler(worker_ProgressChanged);
mWorker.WorkerReportsProgress = true;
mWorker.WorkerSupportsCancellation = true;
mWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
mWorker.RunWorkerAsync();
// Don't do anything else here
}
private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) {
for (int i = 1; i < 100; i++) {
mWorker.ReportProgress(i);
// Do some part of the work
System.Threading.Thread.Sleep(100);
// Check if the user wants to abort
if (mWorker.CancellationPending) {
e.Cancel = true;
return;
}
}
mWorker.ReportProgress(100); // Done
}
private void worker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e) {
pbProcessing.Value = e.ProgressPercentage;
}
private void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) {
// Stop Progressbar updatation
Window1 w = new Window1();
w.Browser.Navigate(new Uri("http://stackoverflow.com"));
w.Show();
// Check the result
if (e.Cancelled) {
// show the message box that the task has been canceled
}
// Reset Progress bar
pbProcessing.Value = 0;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
if (mWorker != null) {
if (mWorker.IsBusy) {
mWorker.CancelAsync();
}
}
}

Categories