Close the form only after task has been completed - c#

I have a cleanup task that deletes a big file on exit.
private async Task DoCleanup()
{
await Task.Run(() =>
{
File.Delete(FilePath);
});
}
Now I want to await that task on exit (FormClosing event), but the form closes before the file was entirely deleted.
I've tried to cancel the event and exit the application manually, like this:
private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
this.Hide();
e.Cancel = true;
await DoCleanup();
Application.Exit();
}
But the Task is called over and over again (like an infinite loop). What should I do?

You can make an event that calls a function and bind the event to Application.Close() when the Task completes.
public async static Task Main(string[] args)
{
YourClassName classInstance = new YourClassName();
// Bind the event to something
classInstance .CompletedTaskEvent += (s, e) => Console.WriteLine("Completed work");
// Start the work
await classInstance.DoCleanup();
Console.ReadLine();
}
public class YourClassName
{
// Some event
public event EventHandler CompletedTaskEvent;
public async Task DoCleanup()
{
await Task.Run(async () =>
{
await Task.Delay(2500);
// When the task completes invoke the event
// And pass the current class instance to the sender
// And you can add any kind of event argument you want
// however I recommend you make the event generic and then pass the argument type
CompletedTaskEvent?.Invoke(this, eventArguments);
});
}
}

Related

C# WPF program button click run a task until another button click stop or until cancel token valid

I am creating a WPF app where I want to have a global bool im assuming, on the first button click I’ll set this bool to true and I want it to run a task (continuously call an API method) until I click the button again and it stops it. What would be the best way to do this?
private bool running = false;
private async void BtnTrade1_Buy_Click(object sender, RoutedEventArgs e)
{
if (!running)
{
running = true;
}
else
running = false;
if (running)
{
RunningNrunnin(running);
//tradeClient.GetTradeHistory();
}
}
public void RunningNrunnin(bool running)
{
if (running)
{
Task task = new Task(() =>
{
while (running)
{
GetTradeHistory();
Thread.Sleep(2000);
}
});
task.Start();
}
}
Added Below
I would like to call a method over and over until the user creates a cancel request on a thread in the background. I currently had it so I can call a action (a counter) and update the GUI each second but when I try to do this same thing with a method call it executes only once.
// Here is the method I want to call continously until canceled
private async void HistoryTest()
{
cancellationToken = new CancellationTokenSource();
task = Task.Factory.StartNew(async () =>
{
while (true)
{
cancellationToken.Token.ThrowIfCancellationRequested();
await Client2.GetHistory();
await Task.Delay(2000);
}
}, cancellationToken.Token);
}
public async Task GetHistory()
{
try
{
var response = await Client.Service.GetDataAsync
(
ProductType.BtcUsd,
5,
1
);
}
catch(Exception)
{
throw;
}
}
I made a little console test app to test this so I had to change the method signatures (static) and can't use ButtonClick on a console. I simulated the button click by putting as sleep between the programatic "button click".
This might get you started.
private static bool isRunning = false;
private static int clickCounter = 0;
private static int iterationsCounter = 0;
static void Main(string[] args)
{
Console.WriteLine(“Start”);
for(int i = 0; i < 7; i++)
{
BtnTrade1_Buy_Click();
System.Threading.Thread.Sleep(1000);
}
Console.WriteLine(“END”);
}
private static async Task BtnTrade1_Buy_Click()
{
iterationsCounter = 0;
isRunning = !isRunning;
Console.WriteLine($"Ha: {isRunning} {clickCounter++}");
await RunningNrunnin();
}
private static async Task RunningNrunnin()
{
await Task.Run(() => Runit());
}
private static void Runit()
{
while (isRunning)
{
GetTradeHistory();
System.Threading.Thread.Sleep(100);
}
}
private static void GetTradeHistory()
{
Console.WriteLine($"Hello Test {iterationsCounter++}");
}
Of course you wouldn't need all the counters and the Console.WriteLine() stuff. They are there to allow you to visualize what is happening.
Let me know if you need more info.
You don't need to do anything else inside the BtnTrade1_Buy_Click event handler, beyond toggling the isRunning field:
private bool _isRunning;
private void BtnTrade1_Buy_Click(object sender, RoutedEventArgs e)
{
_isRunning = !_isRunning;
}
The Task that is getting the trade history in a loop, needs to be started only once. You could start it in the Window_Loaded event. Storing the Task in a private field is a good idea, in case you decide to await it at some point, but if you are handling the exceptions inside the task it's not necessary.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_ = StartTradeHistoryLoopAsync(); // Fire and forget
}
private async Task StartTradeHistoryLoopAsync()
{
while (true)
{
var delayTask = Task.Delay(2000);
if (_isRunning)
{
try
{
await Task.Run(() => GetTradeHistory()); // Run in the ThreadPool
//GetTradeHistory(); // Alternative: Run in the UI thread
}
catch (Exception ex)
{
// Handle the exception
}
}
await delayTask;
}
}
Don't forget to stop the task when the window is closed.
private void Window_Closed(object sender, EventArgs e)
{
_isRunning = false;
}
This will stop the calls to GetTradeHistory(), but will not stop the loop. You may need to add one more private bool field to control the loop itself:
while (_alive) // Instead of while (true)

What is the best patter to call async methods after app start? (Fire&Forget in Constructor?)

In my application I want to setup my ViewModels by queried data (from async methods) right after my application started.
In my case I have a .NET Framework WPF application and after the start I want to begin loading the data by async methods. After awaiting the data I want to update my view models.
What is the best way and the best entry point to do this?
My first approach is the following:
public App()
{
// Do synchronous initializing stuff
// Load data and initialize by viewmodel with it
#pragma warning disable CS4014
ProgramStateViewModel.Instance.LoadDataAndSetupAsync();
#pragma warning restore CS4014
}
The problem with this is I got the compiler warning and I have to handle it by suppressing it.
Actually I want a Fire-and-Forget call, but the method I am calling is async and returns a Task.
How to do it better?
You can attach an async Startup event handler:
public App()
{
Startup += async (s, e) =>
{
await ProgramStateViewModel.Instance.LoadDataAndSetupAsync();
};
}
Try the following, using discards:
public App()
{
// Do synchronous initializing stuff
// Load data and initialize by viewmodel with it
_ = ProgramStateViewModel.Instance.LoadDataAndSetupAsync();
}
By using discards, you can return the Task and you don't declare anything else that will not be used.
Be aware that even without the warning the dangers still apply.
Especially if you have an error in your background work which well not be caught. To avoid this situation, please check another SO question here on managing exceptions on the separate thread.
A possibility is to use the OnStartup function in the App.xaml.cs file. For the asynchronous call, various methods exist, following some methods to make an asynchronous call.
Using BackgroundWorker
public partial class App : Application
{
protected override void OnStartup(System.Windows.StartupEventArgs e)
{
BackgroundWorker _backgroundWorker;
if (_backgroundWorker != null)
{
_backgroundWorker.CancelAsync();
}
_backgroundWorker = CreateBackgroundWorker();
_backgroundWorker.RunWorkerAsync();
}
}
private BackgroundWorker CreateBackgroundWorker()
{
var bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += _backgroundWorker_DoWork;
bw.ProgressChanged += _backgroundWorker_ProgressChanged;
bw.RunWorkerCompleted += _backgroundWorker_RunWorkerCompleted;
return bw;
}
private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
}
private void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
}
private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
}
Using Thread
Method 1
myThread = new Thread(() => CustomTaskAsync());
myThread.Start();
private void CustomTaskAsync()
{
...............
}
Method 2
Thread thread = new Thread(new ThreadStart(CustomTaskAsync));
thread.Start();
public void CustomTaskAsync()
{
..................
}
Method 3
Thread thread = new Thread(CustomTaskAsync);
thread.Start();
public void CustomTaskAsync()
{
...............
}
Using Task
Method 1
bool result ;
Task<bool> task = Task.Run<bool>(async () => await CustomTaskAsync());
str = task.Result;
public async Task<bool> CustomTaskAsync()
{
bool result;
result= await Task.Factory.StartNew(() => CustomTask());
return result;
}
private bool CustomTask()
{
bool result;
return result;
}
Method 2
CustomTaskAsync().Wait();
public async Task CustomTaskAsync()
{
await Task.Run(() => {
.................
});
}

How to wait if the first code is under execution?

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.

Asynchronous methods and a event

I use awesomium to automate a site. I tried to use asynchronous programming, because I didn't want my GUI to freeze, but I have a problem at a event (a popup appear and I want to make some actions in this popup until I close it) when the application doesn't continue how I want. After the event is fired I want my application to continue with the event method (webc_ShowCreatedWebView and after with popupTwitter(method), but I find that when executing JavaScript code, the control returns in the While from the first method. How could I do that after the earnpoints method is called and the events is fired to finish the event and the method and after that the control return in the while.
private async void button4_Click(object sender, EventArgs e)
{
Twitter twitter = new Twitter(webView);
twitter.Login(webView);
webView.ShowCreatedWebView += webc_ShowCreatedWebView;
addmefast.Login(webView);
int i = 0;
while (i < 10)
{
Task earnpoints = EarnPoints(webView);
await earnpoints;
//Here i don't want to continue until EarnPoints method > webc_ShowCreatedWebView event > popupTwitter method it's finished.
i++;
}
}
public async Task EarnPoints(IWebView web)
{
web.Source = "http://addmefast.com/free_points/twitter".ToUri();
await Task.Delay(3000);
web.ExecuteJavascript("document.getElementsByClassName('single_like_button btn3-wrap')[0].click();"); //event fired: webc_ShowCreatedWebView
}
async void webc_ShowCreatedWebView(object sender, ShowCreatedWebViewEventArgs e)
{
WebView view = new WebView(e.NewViewInstance);
await popupTwitter(view);
}
async Task popupTwitter(WebView view)
{
Popupform FormTwitter = new Popupform(view);
FormTwitter.Show();
await Task.Delay(6000);
FormTwitter.Twitter();
await Task.Delay(2000);
FormTwitter.Close();
await Task.Delay(4000);
}
I also had problems implementing async methods with awesomium, but got it working.
First I made this wrapper. Have to be created on the main thread.
public class AsyncWebView
{
public static SynchronizationContext _synchronizationContext;
private readonly WebView _webView;
public AsyncWebView()
{
_synchronizationContext = SynchronizationContext.Current;
_webView = WebCore.CreateWebView(1024, 900);
}
public async Task Navigate(String url)
{
Debug.WriteLine("Navigating");
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
FrameEventHandler handler = (sender, args) =>
{
Debug.WriteLine(args.Url);
if (!_webView.IsNavigating && !_webView.IsLoading)
tcs.SetResult(true);
};
_webView.LoadingFrameComplete += handler;
_synchronizationContext.Send(SetWebViewSource, url);
await tcs.Task;
_webView.LoadingFrameComplete -= handler;
Debug.WriteLine("Done");
}
private void SetWebViewSource(object url)
{
_webView.Source = new Uri((string)url);
}
}
Usage:
async Task test()
{
await webView.Navigate("http://www.nytimes.com");
Debug.WriteLine("All done");
}
Just make sure you have a SynchronizationContext where the AsyncWebView constructor is called from.

Can I use async / await to simulate a background worker?

I'm trying to avoid having to chain a bunch of BackgroundWorkers together. I'm doing something that requires me to wait for the UI to update before continuing execution. Obviously, I can't use Sleep, as this blocks the UI thread from updating and defeats the purpose. I found the code below which I thought was the answer, but it appears the task.Wait(); line is still blocking the UI thread.
static void Main(string[] args)
{
var task = Task.Run(() => DoSomething());
task.Wait();
// once the task completes, now do more
}
static void DoSomething()
{
// something here that is looking for the UI to change
}
I also tried the following, which did the same thing:
static void Main(string[] args)
{
var task = Task.Run(() => DoSomethingAsync());
task.Wait();
// once the task completes, now do more
}
private async Task DoSomethingAsync()
{
// something here that is looking for the UI to change
}
Is it possible to do what I want, and if so, what am I doing wrong?
You need to await the task instead of blocking on it. You can do that inside an async method.
Now, Main can't be async but an event handler can be (which I guess is where you actually use that code):
public async void EventHandler(object sender, EventArgs e)
{
await Task.Run(() => DoSomething()); // wait asynchronously
// continue on the UI thread
}
Note that it's async void which should only be used on event handlers. All other async methods should return a task.
Using Task.Run means your using a ThreadPool thread. To really wait asynchronously for the UI to "do something" you should use TaskCompletionSource. You create it and await it's Task property and you complete that task when the UI changed:
public async void EventHandler(object sender, EventArgs e)
{
_tcs = new TaskCompletionSource<bool>();
await _tcs.Task;
}
public void UIChanged(object sender, EventArgs e)
{
_tcs.SetResult(false);
}

Categories

Resources