Exit from while loop which started inside a async method - c#

How to break the while loop which inside an async method. Please refer below code
I have tried many ways but none of them worked for me
async void TotalTimer(string time)
{
while (true)
{
TimeSpan timesp = DateTime.Now - DateTime.Parse(time);
TotalTime = timesp.Hours + " : " + timesp.Minutes + " : " + timesp.Seconds;
await Task.Delay(1000);
if (string.IsNullOrEmpty(time))
{
break;
}
}
}
I need to stop and exit from the loop
Updated code:
async Task TotalTimer(CancellationToken token)
{
var intime = await App.Database.GetRecentIn();
InTime = DateTime.Parse(intime.datetime).ToString("hh:mm tt");
while (!token.IsCancellationRequested)
{
TimeSpan timesp = DateTime.Now - DateTime.Parse(intime.datetime);
TotalTime = timesp.Hours + " : " + timesp.Minutes + " : " + timesp.Seconds;
Console.WriteLine(TotalTime); // to see it's working
await Task.Delay(1000);
if (token.IsCancellationRequested)
{
break;
}
}
}
void StatCounting()
{
var cts = new CancellationTokenSource();
_= TotalTimer(cts.Token);
}
void StopCounting()
{
var cts = new CancellationTokenSource();
cts.Cancel();
_= TotalTimer(cts.Token);
_=Finalize();
}

Use CancellationToken like this:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp
{
class Program
{
private string TotalTime;
static void Main(string[] args)
{
new Program().Run();
}
private void Run()
{
var cts = new CancellationTokenSource();
TotalTimer("00:00:00", cts.Token);
Console.ReadLine(); // waits for you to press 'Enter' to stop TotalTimer execution.
cts.Cancel();
Console.WriteLine("Press 'Enter' to exit the program.");
Console.ReadLine(); // waits for you to press 'Enter' to exit the program. See, TotalTimer stopped.
}
// your original method modified
async void TotalTimer(string time, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
TimeSpan timesp = DateTime.Now - DateTime.Parse(time);
TotalTime = timesp.Hours + " : " + timesp.Minutes + " : " + timesp.Seconds;
Console.WriteLine(TotalTime); // to see it's working
await Task.Delay(5000, token);
}
}
}
}
Update
#Henk Holterman, according to edits history if (string.IsNullOrEmpty(time)) looks like an attempt to break the loop from outside. But it's pointless for two reasons:
strings are immutable
if time is null or empty DateTime.Parse(time) (it was in the original post) throws before the check
Adding the token to Task.Delay is a good point. Doing so saves resources, though makes no difference to observable behaviour.
Update2
#Argon, the code is independent to wpf, winforms, console or whatever. See minimal wpf example bellow. I checked it's working. If something does not work with you concrete code, you maybe hiding some details from us.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private string TotalTime;
private CancellationTokenSource cts;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
cts = new CancellationTokenSource();
TotalTimer("00:00:00", cts.Token);
}
async void TotalTimer(string time, CancellationToken token)
{
try
{
while (!token.IsCancellationRequested)
{
TimeSpan timesp = DateTime.Now - DateTime.Parse(time);
TotalTime = timesp.Hours + " : " + timesp.Minutes + " : " + timesp.Seconds;
label1.Content = TotalTime;
await Task.Delay(5000, token);
}
}
catch(OperationCanceledException)
{
label1.Content = "Canceled";
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
cts.Cancel();
}
}
}

Using a break in an async method is perfectly fine.
This works fine:
async void Main()
{
await TotalTimer();
Console.WriteLine("Done.");
}
async Task TotalTimer()
{
while (true)
{
await Task.Delay(1000);
if (true)
{
break;
}
}
}
It waits a second and then write Done. to the console.
Your code just doesn't change time so it's never hitting the break.
Other than that your async method should return a Task rather than a void.

Thanks for your effort guys. I have figure out a way to stop the loop. I have defined boolean and checked it within the while loop to break the loop. Its most likely #Enigmativitys answer.
bool hasToStop = false;
while (true)
{
TimeSpan timesp = DateTime.Now - DateTime.Parse(intime.datetime);
TotalTime = timesp.Hours + " : " + timesp.Minutes + " : " + timesp.Seconds;
Console.WriteLine(TotalTime); // to see it's working
await Task.Delay(1000);
if (hasToStop)
{
Console.WriteLine("Stoped");
break;
}
}

Related

Asynchronous function call of Windows Forms C#

I have an asynchronous function. It is called only once when the form is displayed for the first time. My function should ping devices asynchronously when I open the program. But it turns out that when you close the child form, another poll is launched. Tell me where the error may be.
Function call (I tried to call it in formLoad):
private async void MainForm_Shown(object sender, EventArgs e)
{
await Start();
}
Function itself:
public async Task Start()
{
while (keyOprosDev)
{
for (int i = 0; i < devicesListActivity.Count; i++)
{
devicesListActivity[i].DevicesList.DevicesTotalPing++;
string ipAdresDevice = devicesListActivity[i].DevicesList.DevicesName;
int portDevice = devicesListActivity[i].DevicesList.DevicesPort;
int activeDevice = devicesListActivity[i].DevicesList.DevicesActiv;
int sendTimeDevice = devicesListActivity[i].DevicesList.DevicesTimeSend;
int respTimeDevice = devicesListActivity[i].DevicesList.DevicesTimeResp;
using (TcpClient client = new TcpClient())
{
if (activeDevice == 1)
{
client.SendTimeout = sendTimeDevice;
client.ReceiveTimeout = respTimeDevice;
var ca = client.ConnectAsync(ipAdresDevice, portDevice);
await Task.WhenAny(ca, Task.Delay(sendTimeDevice));
client.Close();
if (ca.IsFaulted || !ca.IsCompleted)
{
textBox1.AppendText($"{DateTime.Now.ToString()} Server refused connection." + " " + ipAdresDevice + string.Format(" [{0}/{1}]", devicesListActivity[i].DevicesList.DevicesSuccessPing, devicesListActivity[i].DevicesList.DevicesTotalPing) + " " + System.Math.Round((double)(devicesListActivity[i].DevicesList.DevicesSuccessPing / devicesListActivity[i].DevicesList.DevicesTotalPing * 100)) + " %");
textBox1.AppendText("\r\n");
devicesListActivity[i].DevicesList.DevicesImage = 1;
}
else
{
devicesListActivity[i].DevicesList.DevicesSuccessPing++;
textBox1.AppendText($"{DateTime.Now.ToString()} Server available" + " " + ipAdresDevice + string.Format(" [{0}/{1}]", devicesListActivity[i].DevicesList.DevicesSuccessPing, devicesListActivity[i].DevicesList.DevicesTotalPing) + " " + System.Math.Round((double)(devicesListActivity[i].DevicesList.DevicesSuccessPing / devicesListActivity[i].DevicesList.DevicesTotalPing * 100)) + " %");
textBox1.AppendText("\r\n");
devicesListActivity[i].DevicesList.DevicesImage = 2;
}
}
else
{
}
}
await Task.Delay(interval);
}
}
}
And here is the opening of the child form:
try
{
DbViewer dbViewer = new DbViewer();
dbViewer.FormClosed += new FormClosedEventHandler(refr_FormClosed);
dbViewer.ShowDialog();
}
catch (Exception ex)
{
writeEventInDb(ex.Message);
}
This is the event that handles the closure of the child form:
void refr_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
kryptonTreeView1.Nodes[0].Nodes[0].Nodes.Clear();
kryptonTreeView1.Nodes[0].Nodes[1].Nodes.Clear();
loadIpListFromDb();
loadComListFromDb();
kryptonTreeView1.ExpandAll();
}
catch (Exception ex)
{
writeEventInDb(ex.Message);
}
}
You need to pass a cancellation token in. Somewhere outside of this code you need to create a CancellationTokenSource the best place is probably an property of the form:
class MainForm
{
CancellationTokenSource cts;
...
You then initialize this and pass this to Start():
private async void MainForm_Shown(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
await Start(ct);
}
In your start loop you need to monitor for the cancellation token:
Because you're using a delay to timeout the ConnectAsync() you need the Task.Delay() to know when cancellation is requested so you need to pass the token to Task.Delay():
await Task.WhenAny(ca, Task.Delay(sendTimeDevice,ct));
After the TcpClient.Close() you need to test if the cancellation is requested, and stop loops if it is:
if (ct.IsCancellationRequested)
break;
You'll need to perform the same test in the while loop, and also you should perform it immediately before the ConnectAsync(). While the most likely place you will encounter ct.IsCancellationRequested == true will be immediately after the Task.WhenyAny or immediately after the Loop interval there's no point starting a ConnectAsync() if cancellation has been requested.
You should also pass the CancellationToken to the Loop interval, otherwise you could end up waiting interval before your form closes:
// This will throw an OperationCancelled Exception if it is cancelled.
await Task.Delay(interval,ct);
Because you're going to continue anyway and just exit if Cancellation is registered you could avoid writing a separate try/catch that does nothing and await the interval like this, it's almost certainly less efficient, but it's cleaner.
// Leave any exceptions of Task.Delay() unobserved and continue
await Task.WhenAny(Task.Delay(interval,ct));
Finally you need to dispose of the CancellationTokenSource, I guess you would do this in something like a MainForm_Closed() function?
private void MainForm_Closed(object sender, EventArgs e)
{
cts.Dispose();
The only thing left to do is to work out when you want to fire the CancellationRequest, based on what you have said you want to do this when the form close button has been clicked, so:
private void MainForm_Closing(object sender, EventArgs e)
{
cts.Cancel();
That will cause the CancellationToken to transition to a cancelled state and your Start() routine will see that and exit.
In your code there is no single place to check for the CancellationToken being set, the rule of thumb is to check for it before and after any await and in your case you should check for it in both the while and the for loop.

How to get handle of an awaitable Task?

I am a newbie in Tasks and still learning this topic so be gentle with me (I think I have some fundamental mess-ups with my below code...)
Please look at the below exercise which will help me describe my question:
I have a simple "MyService" class which has a "Do_CPU_Intensive_Job" method called by the "Run" method. My purpose is to be able to run several instances of the "Do_CPU_Intensive_Job" method (which itself run on a different thread than the UI as its CPU bound), sometimes synchronously and sometimes asynchronously.
In other words, assuming I have 2 instances of MyService, sometimes I want these 2 methods running together and sometimes not.
class MyService
{
private bool async;
private string name;
private CancellationTokenSource tokenSource;
private CancellationToken token;
private bool isRunning = false;
private Task myTask = null;
public MyService(string name, bool async)
{
this.name = name;
this.async = async;
}
public string Name { get { return name; } }
public bool IsRunning { get { return isRunning; } }
public async Task Run ()
{
isRunning = true;
tokenSource = new CancellationTokenSource();
token = tokenSource.Token;
if (async)
myTask = Do_CPU_Intensive_Job();
else
await Do_CPU_Intensive_Job(); // I cannot do myTask = await Do_CPU_Intensive_Job(); so how can the "Stop" method wait for it??
}
public async Task Stop ()
{
tokenSource.Cancel();
if (myTask != null)
await myTask;
isRunning = false;
}
private async Task Do_CPU_Intensive_Job ()
{
Console.WriteLine("doing some heavy job for Task " + name);
int i = 0;
while (!token.IsCancellationRequested)
{
Console.WriteLine("Task: " + name + " - " + i);
await Task.Delay(1000);
i++;
}
Console.WriteLine("Task " + name + " not yet completed! I need to do some cleanups");
await Task.Delay(2000); //simulating cleanups
Console.WriteLine("Task " + name + " - CPU intensive and cleanups done!");
}
}
So, I have the below GUI which which works well but only if the 2 instances are running asynchronously. "works well" means that when stopping the tasks, it stops nicely by running entire "Do_CPU_Intensive_Job" method. hence the last message will be from the GUI ("Both tasks are completed...now doing some other stuff"). So far so good.
public partial class Form1 : Form
{
List<MyService> list = null;
MyService ms1 = null;
MyService ms2 = null;
public Form1()
{
InitializeComponent();
list = new List<MyService>();
ms1 = new MyService("task 1", true);
ms2 = new MyService("task 2", true);
list.Add(ms1);
list.Add(ms2);
}
private async void button1_Click(object sender, EventArgs e)
{
foreach (MyService item in list)
await item.Run();
}
private async void button2_Click(object sender, EventArgs e)
{
foreach (MyService item in list)
{
if (item.IsRunning)
{
await item.Stop();
Console.WriteLine("Done stopping Task: " + item.Name);
}
}
//now ready to do some other stuff
Console.WriteLine("Both tasks are completed...now doing some other stuff");
}
}
Problem starts when the 2 instances are not running simultaneously. In that case, I get the "Both tasks are completed...now doing some other stuff" from the GUI before "Do_CPU_Intensive_Job" is really completed...
ms1 = new MyService("task 1", false);
ms2 = new MyService("task 2", false);
this is not happening when both tasks are running together because I have the handle (myTask) when running asynchronously which I dont when running synchronously.
await Do_CPU_Intensive_Job(); // I cannot do myTask = await Do_CPU_Intensive_Job(); so how can the "Stop" method wait for it??
Thanks, all
I spent some time hammering out the code to a point that I think it is doing what is expected.
The first problem I found is that you can't just pass the cancellation token into your method, you need to relate it to the task(s) that are to be cancelled. Unfortunately I could not find a way to do this directly on an async method but have a look at the MyService class here for how I was able to do this.
class MyService
{
private bool async;
private string name;
private CancellationTokenSource tokenSource;
private bool isRunning = false;
private Task myTask = null;
public MyService(string name, bool async)
{
this.name = name;
this.async = async;
}
public string Name { get { return name; } }
public bool IsRunning { get { return isRunning; } }
public async Task Run()
{
isRunning = true;
tokenSource = new CancellationTokenSource();
myTask = Task.Run(() => Do_CPU_Intensive_Job(tokenSource.Token), tokenSource.Token);
if (!async)
await myTask;
}
public async Task Stop()
{
tokenSource.Cancel();
if (myTask != null)
await myTask;
isRunning = false;
}
private void Do_CPU_Intensive_Job(CancellationToken token)
{
Console.WriteLine("doing some heavy job for Task " + name);
int i = 0;
while (!token.IsCancellationRequested)
{
Console.WriteLine("Task: " + name + " - " + i);
Thread.Sleep(1000);
i++;
}
Console.WriteLine("Task " + name + " not yet completed! I need to do some cleanups");
Thread.Sleep(1000);
Console.WriteLine("Task " + name + " - CPU intensive and cleanups done!");
}
}
The Run method is now using Task.Run to call Do_CPU_Intensive_Job and if you note I am passing the token to both the work method and to the Task.Run call. The latter is what links the token to that Task/Thread and the former is what allows us to watch for the cancellation request.
The final piece is how we call Run on the service instances, by calling await on a Task or async method the thread is being released but the remainder of the code in the method is extracted and will not be run until the awaited task completes.
I was just using a unit test in order to work on the code rather than a button but the premise should be the same, but here is how I was able to run the tasks in synchronous mode and still be able to call stop on them.
var service1 = new MyService("task 1", false);
var service2 = new MyService("task 2", false);
service1.Run(); //Execution immediately moves to next line
service2.Run(); // Same here
await service1.Stop(); //Execution will halt here until task one has fully stopped so task 2 actually continues running
await service2.Stop();

C# TAP async Multiple enqueue Delay then dequeue all

I have a system where a method is called with an object and the database is written with a different object with a list of the first .
Currently :
private async Task SaveAMessage(Messsage message)
{
var messages = new List<Message>();
messages.Add(message);
var envelope = new Envelope();
envelope.messages = messages;
await _db.Save(envelope);
}
But I can only run _db.Save every 1 second.
What is the TAP way of saying: Add this item to the list and then save them all together after 1 second. Below I have some fake code that expresses what I wish I could write.
Javascript-y Pseudo Code:
private List<Message> messages = new List<Message>();
private int? valueCheckTimer;
private async Task SaveAMessage(Messsage message)
{
messages.Add(message);
if (valueCheckTimer) {
return;
}
valueCheckTimer = setTimeout(function () {
var envelope = new Envelope();
envelope.messages = messages;
await _db.Save(envelope);
messages.Clear();
},1000);
}
How do I write C# code that acts the way the pseudo-code works?
You can actually do this with just one small really simple change. Add a WhenAll to SaveAMessage to await both Save as well as a call to Task.Delay:
private async Task SaveAMessage(Messsage message)
{
var messages = new List<Message>();
messages.Add(message);
var envelope = new Envelope();
envelope.messages = messages;
await Task.WhenAll(_db.Save(envelope), Task.Delay(1000));
}
Now you can just loop through all of your calls to SaveAMessage, awaiting them all, and you can be sure that it waits until the previous save is done and that at least a second has passed before continuing.
If you sometimes don't need to wait a full second when using SaveAMessage elsewhere, then simply pull this change out and have whatever code you're using to save all of your messages await the Task.Dealy call.
Try this piece of c# code (run as ConsoleApp):
namespace ConsoleApplication1
{
public class Program
{
private static async Task<string> SaveAMessage(string message)
{
var messages = new List<string>();
messages.Add(message);
return await save(messages);
}
private static Task<string> save(List<string> msg)
{
Task<string> task = Task.Factory.StartNew<string>(() =>
{
Console.WriteLine("Message " + msg[0] + " received...");
Console.WriteLine("Message " + msg[0] + " running...");
Thread.Sleep(3000);
return "Message " + msg[0] + " finally return.";
});
return task;
}
public static void Main(string[] args)
{
Task<string> first = SaveAMessage("Msg1");
first.ContinueWith(x => Console.WriteLine("Print " + x.Result));
Task<string> second = SaveAMessage("Msg2");
second.ContinueWith(x => Console.WriteLine("Print " + x.Result));
Task<string> third = SaveAMessage("Msg3");
third.ContinueWith(x => Console.WriteLine("Print " + x.Result));
Task<string> fourth = SaveAMessage("Msg4");
fourth.ContinueWith(x => Console.WriteLine("Print " + x.Result));
Task<string> fifth = SaveAMessage("Msg5");
fifth.ContinueWith(x => Console.WriteLine("Print " + x.Result));
Console.ReadKey();
}
}
}

await async lambda in ActionBlock

I have a class Receiver with an ActionBlock:
public class Receiver<T> : IReceiver<T>
{
private ActionBlock<T> _receiver;
public Task<bool> Send(T item)
{
if(_receiver!=null)
return _receiver.SendAsync(item);
//Do some other stuff her
}
public void Register (Func<T, Task> receiver)
{
_receiver = new ActionBlock<T> (receiver);
}
//...
}
The Register-Action for the ActionBlock is a async-Method with a await-Statement:
private static async Task Writer(int num)
{
Console.WriteLine("start " + num);
await Task.Delay(500);
Console.WriteLine("end " + num);
}
Now what i want to do is to wait synchronously (if a condition is set) until the action method is finished to get an exclusive behavior:
var receiver = new Receiver<int>();
receiver.Register((Func<int, Task) Writer);
receiver.Send(5).Wait(); //does not wait the action-await here!
The Problem is when the "await Task.Delay(500);" statement is executed, the "receiver.Post(5).Wait();" does not wait anymore.
I have tried several variants (TaskCompletionSource, ContinueWith, ...) but it does not work.
Has anyone an idea how to solve the problem?
ActionBlock by default will enforce exclusive behavior (only one item is processed at a time). If you mean something else by "exclusive behavior", you can use TaskCompletionSource to notify your sender when the action is complete:
... use ActionBlock<Tuple<int, TaskCompletionSource<object>>> and Receiver<Tuple<int, TaskCompletionSource<object>>>
var receiver = new Receiver<Tuple<int, TaskCompletionSource<object>>>();
receiver.Register((Func<Tuple<int, TaskCompletionSource<object>>, Task) Writer);
var tcs = new TaskCompletionSource<object>();
receiver.Send(Tuple.Create(5, tcs));
tcs.Task.Wait(); // if you must
private static async Task Writer(int num, TaskCompletionSource<object> tcs)
{
Console.WriteLine("start " + num);
await Task.Delay(500);
Console.WriteLine("end " + num);
tcs.SetResult(null);
}
Alternatively, you could use AsyncLock (included in my AsyncEx library):
private static AsyncLock mutex = new AsyncLock();
private static async Task Writer(int num)
{
using (await mutex.LockAsync())
{
Console.WriteLine("start " + num);
await Task.Delay(500);
Console.WriteLine("end " + num);
}
}

Thread Abort Exception in.NET 4.5

From IIS, when I am calling some background task in a new thread, it only runs through if the task does not contain certain asynchronous calls.
If I call a background task in a new thread, that does contain these asynchronous calls, it does return a ThreadAbortException, while the same Action, being executed synchronously inside ApiController, does run through, and a different action, called asynchronously, also runs through.
Furthermore, when I call one action synchronously as well as the other action asynchronously, the asynchronous call fails as well.
What does cause the ThreadAbortException?
Is there anything I can do to get around the ThreadAbortException?
Code:
[HttpGet]
public string TestThreadAbortException()
{
InitToolkit(); // Initialize Logger, DB etc.
DebugController.DoAfter(5.Seconds(), MyAction); // Runs through!
//TestThreadAbortException(logger); // Runs through!
//Combining the first and the second line makes the first one throw the Exception as well.
//DebugController.DoAfter(10.Seconds(), TestThreadAbortException); // throws Exception
return String.Join("\r\n",logger.Flush());
}
private void TestThreadAbortException(Logger logger)
{
Task<string> task = new Task<string>(MyMethod);
task.Start();
Task.Run(async () => await task);
try
{
var result = ConfigureAwait(task, false).Result;
}
catch (System.AggregateException ex)
{
if (ex.InnerExceptions.Count == 1)
{
throw ex.InnerExceptions[0];
}
throw;
}
}
private async Task<string> ConfigureAwait(Task<string> task, bool continueOnCapturedContext)
{
return await task.ConfigureAwait(continueOnCapturedContext: continueOnCapturedContext);
}
private string MyMethod()
{
Thread.Sleep(20000);
return "Test";
}
private void MyAction(Logger logger)
{
logger.Log(MyMethod());
}
public static void DoAfter(TimeSpan waitFor, Action<Logger> action)
{
try {
ThreadStart work = () =>
{
Thread.Sleep(waitFor);
DatabaseLogger logger = new DatabaseLogger();
logger.Log("Executing " + action.Method.Name + ", " + DateTime.Now.ToLongTimeString());
try
{
action.Invoke(logger);
logger.Log("Successfully executed " + action.Method.Name + ", " + DateTime.Now.ToLongTimeString());
}
catch (Exception e)
{
logger.Log("Error in " + action.Method.Name + ": " + e.Message + ", " + DateTime.Now.ToLongTimeString());
}
logger.CloseDatabase();
};
Thread thread = new Thread(work);
thread.Start();
}
catch
{
}
}
Background information: In the production code, the inner async call, where during debugging I just create a new task, is created inside a Microsoft library that does not offer synchronous methods, so I won't be able to just "remove the Task".
What causes the ThreadAbortException?
See ThreadAbortException: "The exception that is thrown when a call is made to the Abort method". Avoid using all the manual Thread related code in the DoAfter method.
Is there anything I can do to get around the ThreadAbortException?
Yes... utilize the async and await keywords correctly following best programming practices and patterns.
Here are my suggested modifications:
[HttpGet]
public async Task<string> TestThreadAbortException()
{
InitToolkit(); // Initialize Logger, DB etc.
var result = await DoAfter(5.Seconds(), MyAction);
return result;
}
Mark your controller method as Task<T> returning, where T is the type to return. In this case a string.
If you need to simply start a background logging job and then return to the client, you should consider QueueBackgroundWorkItem. Instead of using Thread.Sleep use Task.Delay, mark methods as Task or Task<T> that are representative of asynchronous operations.
public async Task<T> DoAfter<T>(TimeSpan waitFor, Func<Logger, Task<T>> action)
{
await Task.Delay(waitFor);
DatabaseLogger logger = new DatabaseLogger();
logger.Log("Executing " + action.Method.Name + ", " + DateTime.Now.ToLongTimeString());
try
{
return await action(logger);
logger.Log("Successfully executed " + action.Method.Name + ", " + DateTime.Now.ToLongTimeString());
}
catch (Exception e)
{
logger.Log("Error in " + action.Method.Name + ": " + e.Message + ", " + DateTime.Now.ToLongTimeString());
}
finally
{
logger.CloseDatabase();
}
}
private async Task MyAction(Logger logger)
{
var result = await MyMethod();
logger.Log(result);
}
private async Task<string> MyMethod()
{
await Task.Delay(20000);
return "Test";
}

Categories