I have a situation where a user creates the instance of a class when the user clicks on a tabitem. This class contains a function which plots a graph and it's very time consuming so it is written async.
Now the problem is lets say the user first time click on tab and the class instantiates and the long process works for long time and meanwhile where the previous async task is not finshed and the user clicked again on the same tabitem.
In this situation i must wait until the previous async task is not finished and then on second click to tabitem must create instance after teh fist async task is finshed (It should wait until the first async process is not finshed).
The code is here:
if (selectedTabIndex == 2) //What is the user selected second time whil the previous task is still not finshed ?
{
DrawGraph obj= new DrawGraph(selectedItem.Name);
}
Somewhere in DrawGraph class constructor i have done:
public DrawGraph(string Name)
{
timeConsumingProcess(Name);
}
public async void timeConsumingProcess(string Name)
{
await startTaimeConsumingProcess();
}
What i want is when user clicks the second time this tab item number=2 then it must wait until the previous async task to finsh and then it must instantiate again DrawGraph class to
restart async again.
How to achieve it ?
In this situation i must wait until the previous async task is not finished and then on second click to tabitem must create instance after teh fist async task is finshed
Then have your code (a)wait on the last task:
private Task drawGraph = null;
...
if (selectedTabIndex == 2)
{
if (drawGraph != null)
await drawGraph;
DrawGraph obj = new DrawGraph(selectedItem.Name);
drawGraph = obj.timeConsumingProcess();
}
...
private readonly string name;
public DrawGraph(string Name)
{
name = Name;
}
public async Task timeConsumingProcess()
{
await startTaimeConsumingProcess();
}
Note that this requires you to use async Task instead of async void, which is good anyway because you should avoid async void.
You can store the Task representing the long running action in an instance variable in the form and use this to check whether the task is still running.
private Task drawGraphTask = null;
private void button1_Click(object sender, EventArgs e)
{
DrawGraph();
}
private async void DrawGraph()
{
// Only perform task when this is the first time
// or the previous task is already completed
if (drawGraphTask == null || drawGraphTask.IsCompleted)
{
drawGraphTask = startTimeConsumingProcess();
await drawGraphTask;
}
else
{
MessageBox.Show("Task already active");
}
}
private Task startTimeConsumingProcess()
{
// Your Code here
return Task.Delay(5000);
}
It is not good to start an asynchronous activity from inside a constructor. Move the asynchronous logic outside the DrawGraph class and make instantiating the class asynchronous at the level of the form already.
If it is a very time consuming process, you can choose to do it in a different thread
public DrawGraph(string Name)
{
var task = new Task(() => timeConsumingProcess(Name));
task.Start();
}
This way your main thread won't be blocked. If you want to run some code after this long running task is finished, use the ContinueWith method.
public DrawGraph(string Name)
{
_canPressTab = false;
var task = new Task(() => timeConsumingProcess(Name));
task.Start();
task.ContinueWith(t => {
_canPressTab = true;
...
});
}
UPDATE BELOW
As seen here, you can call the Draw method when you click on a tab. This checks if a task is already running. If not, it starts the task. If the task is running it will wait on it for it to complete and then start a new task.
public Task task = new Task(DoSomething);
private static void DoSomething()
{
Thread.Sleep(10000);
}
private void Draw()
{
//if tab is clicked run the Draw method
if (task.Status.Equals(TaskStatus.Running))
{
task.Wait();
task = new Task(DoSomething);
task.Start();
}
else if (task.Status.Equals(TaskStatus.RanToCompletion))
{
task = new Task(DoSomething);
task.Start();
}
else
{
task.Start();
}
}
A simple change will achieve this
public async Task DrawGraph(string Name)
{
await timeConsumingProcess(Name);
}
public async Task timeConsumingProcess(string Name)
{
await startTaimeConsumingProcess();
}
I made DrawGraph() return a Task as well in case you need to await it.
Related
I have a background task in my software which should run indefinitely.
The code repeating the task (and possibly other tasks in the future) looks like this:
public class RunTicketTasks
{
public async Task RunBackgroundTasks()
{
AssignTickets assignTickets = new AssignTickets();
while (true)
{
await assignTickets.AssignTicketCreator();
await Task.Delay(5 * 60 * 1000);
}
}
}
At the same time I have the WPF UI MainWindow.xaml which is the application entry point as far as I know.
within public MainWindow I start the task like the following way:
public MainWindow()
{
InitializeComponent();
JiraBackgroundTasks.RunTicketTasks ticketTasks = new JiraBackgroundTasks.RunTicketTasks();
ticketTasks.RunBackgroundTasks();
}
Apparently, this starts the task on the same thread (I believe). The task is started and running successfully, but the UI is beeing blocked due to long running operations. Meanwhile, when I uncomment the last line ticketTasks.RunBackgroundTasks(); the UI just runs fine.
How do I start the task in the background so that my User Interface is still responsive?
EDIT:
The reason I started the task this way was because of exception handling.
I can successfully start the task on a different thread as suggested with Task.Run(async () => await ticketTasks.RunBackgroundTasks()); But then I will not receive any exception if something goes wrong.
How can I start the task not blocking the UI and still receive the exception details if an exception is thrown?
The Internet states the following method:
await Task.Run(() => ticketTasks.RunBackgroundTasks());
But this will not work because The await operator can only be used within an async method.
One way to do it is to wrap the task in an async event callback:
public partial class MainWindow : Window
{
// I mocked your missing types, replace with your own
class AssignTickets
{
public Task AssignTicketCreator() => Task.CompletedTask;
}
public class RunTicketTasks
{
public async Task RunBackgroundTasks()
{
AssignTickets assignTickets = new AssignTickets();
int x = 0;
while (true)
{
await assignTickets.AssignTicketCreator();
await Task.Delay(1000);
var isRunningOnDispatcher = Application.Current.Dispatcher ==
System.Windows.Threading.Dispatcher.FromThread(Thread.CurrentThread);
Trace.WriteLine($"One second passed, running on dispatcher: {isRunningOnDispatcher}");
// exception will also get thrown on the main thread
if (x++ > 3) throw new Exception("Hello!");
}
}
}
public MainWindow()
{
InitializeComponent();
// event fires on UI thread!
this.Loaded += async (sender, args) =>
{
try
{
await new RunTicketTasks().RunBackgroundTasks();
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
};
}
}
I have two pages. The first page is a menu and the second page is the data. I choose an option in the menu and that triggers an async method that downloads the data from the database which is then displayed on page 2.
In my mind I have to options on how to implement this and I can't figure out which one is better.
Option 1 - something like this:
public void NavigateToPageTwo()
{
var myData = Task.Run(async () => { return await GetData(); }).Result;
Navigate(myData);
}
public async Task<MyData> GetData()
{
return await some method...
}
Run GetData synchoniously blocking the thread while the user stares at a spinning wheel and then when the data has arrived move onto the next page and pass the data as a parameter or set it as a static field somewhere doesn't matter just do nothing untill the data has arrived.
Option 2
public async void NavigateToPageTwo()
{
subscribe to an event here that will trigger when data arrives
await GetData();
}
public async Task GetData()
{
await some method...
fire an event after data has arrived
}
void OnEvent()
{
navigate to page two after the event has fired
}
Whatis the most appropriate approach out of these two or maybe some other way is better. Thank you.
Use Wait() option in Task
public void NavigateToPageTwo()
{
var task = GetData();
task.Wait(); //Wait till finish the task
var myData = task.Result;
Navigate(myData);
}
public async Task<MyData> GetData()
{
return await some method...
}
When I call BuildCustomer.StartTask, I then call a method WriteToDatabase. Inside WriteToDatabase, I want to send a status back to the MainForm to write the status to the GUI. When the code reaches that point, my application freezes up and gives no error. I did find out that if I remove task.Wait(), it stops freezing and works. But I think I want the wait in because my BuildCustomer takes a bit of time and writes a lot of updates (including more updates from Common class) to the GUI. Can someone tell me what is wrong or what I should be doing differently? This is a .Net 4 project so I cannot use async, which I've seen other answers for.
public partial class MainForm : Window
{
public MainForm()
{
Common.SendMessage += UpdateStatus;
}
private void Button_Click(object sender, EventArgs e)
{
BuildCustomer.StartTask();
}
private void UpdateStatus(string message)
{
Dispatcher.Invoke(new Action(() =>
{
StatusTextBox.Text = message;
}));
}
}
public class BuildCustomer
{
public static void StartTask()
{
var action = new Action<object>(BuildCustomer);
var task = new Task(() => action(buildDetails));
task.Start();
task.Wait();
}
private void BuildCustomerDetails(object buildDetails)
{
Common.WriteToDatabase();
}
}
public class Common
{
public delegate void MessageLogDelegate(string message);
public static event MessageLogDelegate SendMessage;
public static void WriteToDatabase()
{
SendMessage("Some status message to write back to the GUI");
}
}
You have a deadlock. The StartTask waits on task.Wait() to complete but this occurs (is called on) on the calling thread which is the main UI thread.
The Task being waited eventually reaches UpdateStatus which calls an Invoke on the UI thread as well but this thread is currently waiting on task.Wait() (so it is blocking which results in the UI thread not being available indefinitely).
Try to add async keyword to method signature and use this:
await task;
It cause to does not sleep main thread(UI thread).
How can i know if an async(awaitable) operation is already running and waiting for completion in the application. I have two buttons in a view each binded to two differrent async methods. If button1 is clicked and the async is started and waiting for its result. And at that time if the button2 is clicked. I need to show a message that an already running async methos is there and stop the second async method from executing. How can i achieve this?
Store the task and check for IsCompleted Property.
private Task pendingTask = Task.FromResult(0);
private async void Button1Click()
{
if (!pendingTask.IsCompleted)
{
//Notify the user
return;
}
pendingTask = DoSomethingAsync();
await pendingTask;
...
}
private async void Button2Click()
{
if (!pendingTask.IsCompleted)
{
//Notify the user
return;
}
pendingTask = DoSomethingElseAsync();
await pendingTask;
...
}
As noted by #Peter Ritchie in comments, better idea is to disable the other button when asynchronous operation is pending. You may consider using it.
Task class has a Status property which can be used to evaluate if an asynchronous operation is running or it has completed or even if it's in faulted state.
You can store the executed Task inside your form and look up its Status property:
public class Form1
{
private Task fooTask = Task.FromResult(0);
public Task FooAsync()
{
return Task.FromResult(0);
}
public async void MyEventHandler(object sender, EventArgs e)
{
if (fooTask.Status == TaskStatus.Running)
{
// If we got here, the task is currently running. Notify someone
return;
}
// If we're here, the task isn't running.
}
}
Note this doesn't take care of situations where your task might be in a Faulted or Canceled state, which you may want to handle as well.
This question is trivial and it is for readability. I would just like to know weather the following line of code has any alternative ? Is that code is correct in the means of Readability and style?
Task newTask = new Task(() => { });
EDIT:
This task will be created when certain condition/rule met. In that case i would assign an Action to this class.
if(condition Met && newTask.Status != TaskStatus.Running )
{
newTask = Task.Factory.StartNew(Action);
}
Thanks
A Task object is not mean to be run multiple times and started and stopped at will. If you cancel a task or it runs to completion you are meant to create a new Task object the next time you want to execute it again. You can keep a reference to your task object and cancel it using a CancellationTokenSource.
I would suggest simply keeping track of whether the task is running by either a bool variable or a Task variable itself where a null value indicates the task isn't running. For example:
private CancellationTokenSource _tokenSource = new CancellationTokenSource();
private Task _task;
public void StartDoingSomething()
{
if (_task == null)
{
_task = Task.Factory.StartNew(Worker, _tokenSource.Token)
.ContinueWith(_ => _task = null);
}
}
public void StopDoingSomething()
{
if (_task != null)
{
_tokenSource.Cancel();
}
}
private void Worker()
{
while (!_tokenSource.IsCancellationRequested)
{
// Do some unit of work
}
}