I'm trying to write integration tests using xunit.
my test application is communicating with my testee application through mqtt and for one of my test I want to send a request, collect all received message for an amount of time and then analyse the result to determine if the outcome is what i expect.
I could sucessfuly do it, however, not beeing very familiar with async programing in c#, I have some doubt about the way I do it.
Can someone tell me if the core idea is right and how can it be improved ? especially regarding some comments in the code.
First my test code:
[Fact]
public void HandlingMultipleSimultanuousRequests()
{
_output.WriteLine("Starting test <HandlingMultipleSimultanuousRequests>");
bool success = false;
_com.SendRequestEvent(new RequestScanEvent());
Task<IList<IScanEvent>> waitTask = WaitAndReturnAll(5);//get all result received the next 5 sec TODO: place before send request, but not running asynchronously (problem for another time)
IList<IScanEvent> results = waitTask.GetAwaiter().GetResult(); //wait synchronously on the results
//TODO: test of the validity of what was received
Assert.True(results.Count > 0); //TODO: dummy test to replace
}
Then the method I use to wait for the results :
private async Task<IList<IScanEvent>> WaitAndReturnAll(int waitTimeSeconds)
{
IList<IScanEvent> scanEvents = new List<IScanEvent>();
try
{
_com.ReceivedScanResult += ReceivedScanEvent;
_genericScanEventReceived = new TaskCompletionSource<IScanEvent>();
DateTime startTime = DateTime.Now;
while(DateTime.Now < startTime.AddSeconds(waitTimeSeconds))
{
if (_genericScanEventReceived.Task.Wait(5)) //small error in timespan ms not really a problem
{
//TODO: should I put a lock here for the receive event...
scanEvents.Add(_genericScanEventReceived.Task.Result);
_genericScanEventReceived = new TaskCompletionSource<IScanEvent>(); //new event to be ready for next message...
//TODO: remove the lock here...
}
}
_com.ReceivedScanResult -= ReceivedScanEvent; //remove callback before cancellation
_genericScanEventReceived.SetCanceled();
_genericScanEventReceived = null;
}
catch (Exception e)
{
_logger.LogError(e, "Error during awaiting of request answer, result should still be valid");
return null;
}
return scanEvents;
}
and finally the eventHandler for when I receive a message from the testee:
private void ReceivedScanEvent(object? sender, IScanEvent res)
{
//TODO: should event queue with a lock, will it work ?
if (_genericScanEventReceived == null)
{
_logger.LogWarning("Received scanevent but no test is currently waiting");
return;
}
if (_genericScanEventReceived.Task.IsCompleted)
{
//TODO: code smell... this case should not append
_logger.LogError("A result is already returned, cannot handle the event now...");
return;
}
_genericScanEventReceived.SetResult(res);
}
As you can see I use a TaskCompletionSource to collect the data. is it the right tool ?
Also I have some concern about the risk of loosing some messages with my code.
Related
I'm trying to use System.Net.Http.HttpClient to return the results of a call to a webpage, so that it implements a POST request. I don't really want to perform this asynchronously. My requirement is to wait until all the data is returned before continuing, so ideally I want synchronous method. However, sadly, it is not possible to just use HttpClient that way.
I've declared the following method, which is asynchronous, which takes a URL and key-value pairs to populate $_POST in the PHP:
private async Task<string> PostRequest(string cUrl, params string[] aParams)
{
HttpClient oClient;
Dictionary<string, string> oArgs;
int iA;
FormUrlEncodedContent oContent;
HttpResponseMessage oResponse;
// check we have an event number of parameters
if ((aParams.GetUpperBound(0) % 2) != 1) throw new Exception(
"Non-even number of parameters passed. Parameters are key-value pairs.");
// put the parameters into a dictionary
oArgs = new Dictionary<string, string>();
for (iA = 0; iA < aParams.GetUpperBound(0); iA += 2)
oArgs.Add(aParams[iA], aParams[iA + 1]);
oClient = new HttpClient();
oContent = new FormUrlEncodedContent(oArgs);
oClient.Timeout = new TimeSpan(0, 0, 10);
oResponse = await oClient.PostAsync(cUrl, oContent);
return await oResponse.Content.ReadAsStringAsync();
}
Now, annoyingly this has to be an asynchonous method. Ho hum. Ideally, I'd like to call it thus:
private void button2_Click(object sender, EventArgs e)
{
var cResult = await PostRequest("http://mywebsite.com/mypage.php",
"MagicToken", "12345",
"Method", "GetSomeData");
txt.Text = cResult.ToString();
}
But I have the compile time error:
The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
What I'm doing above is (obviously) a test. In reality the button that kicks this off is a "Next >" in a wizard. It will use the results to populate a structure with data that other code in the wizard then accesses. I don't the above to occur asynchronously as I don't want other code touching that structure until it is populated.
My question is, how can I wrap a call to PostRequest so that I can wait for all the results to come in (some sort of ...while still processing...wait... loop) and then just return the results of the call, and use that without having to bubble async declarations up through my code?
As a second question, if I have to declare my cmdNext_Click as async, what happens if the user clicks it twice? I specifically want the UI thread to stop until the data is returned and processed.
Edit:
I've tried creating a wrapper function (which is non-async) thus:
private bool PostRequest2(string cUrl, ref string cResponse, params string[] aParams)
{
// This posts a request to the URL, using the parameters passed in aArgs. The response is returned in cResponse.
// cUrl - the URL to POST to
// cResponse - the response returned
// aParams - an even number of parameters, which are key-value pairs. The first of each pair is the name of the item. The second is its value.
int iWaitCount;
try
{
var response = PostRequest(cUrl, aParams);
Console.WriteLine(response);
iWaitCount = 0;
while (!response.IsCompleted)
{
Console.WriteLine("iWaitCount = " + iWaitCount.ToString());
Console.WriteLine("Status = " + response.Status.ToString());
response.Wait(500);
iWaitCount++;
}
cResponse = response.Result;
return true;
}
catch (Exception ex)
{
_g.Errs.Raise(ex);
return false;
}
}
This compiles correctly, but sits in the wait loop indefinitely with response.Status = WaitingForActivation.
There has to be a way to wrap an asynchronouns function in a synchrnous one. The alternative is to have to change all the return types (which are mostly bool - true on success) to Task, which I cannot then use in conditional statements - I have to await them instead. I've realised that this is the fundimental question, and this is a duplicate of: How to call asynchronous method from synchronous method in C#? which refers to await being a zombie virus that infects your code; this appears to be the case.
You can make your button void async (I would maybe return Task instead of void though)
await should mean that your method waits for the PostAsync call to complete.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/await
The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes.
So this is essentially a synchronous call.
Now if you really don't want that void to be async, here's what I can remember off the top of my head:
In .NET 5+, you can use HttpClient.Send which is synchronous. (takes HttpRequestMessage)
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.send?view=net-5.0
Otherwise, you would need to do a .Result if you wanted to get the response. This type of consumption of async methods has been frowned upon in my experience.
Disable button2 until the operation is completed and use async inside the button2 click event.
change button2_Click to :
private async void button2_Click(object sender, EventArgs e)
{
button2.Enabled = false;
var cResult = await PostRequest("http://mywebsite.com/mypage.php",
"MagicToken", "12345",
"Method", "GetSomeData");
txt.Text = cResult.ToString();
button2.Enabled = true;
}
After much reading, and thank you to those above, I've got a working method now. It's nor perfect, but it works in this scenario where I'm calling one async method at a time, and wait processing to stop until it returns.
PostRequest above works correctly, but it must be declared async and called with await. Within my app, I have a variety of callers of it, which must also be declared async and use await when they call it. An example is:
private async Task<bool> ReadProductPrice()
{
string cCsv = "";
try
{
cProductCode = scSubscriptionType.GetSelectedKey().ToString();
var oResponse = await PostRequest("http://mywebsite.com/mywebpage.php",
"MagicToken", "12345",
"Query", "GetProductPrice",
"ProductCode", cProductCode);
if (oResponse == null) throw new Exception("Could not acquire product price from server. (1)");
cCsv = oResponse.ToString();
moProductPrice = new Dataset(_g);
if (!moProductPrice.ReadFromCsv(cCsv)) throw new Exception("Could not decode server response.");
if (moProductPrice.RecordCount != 1) throw new Exception("Could not acquire product price from server. (2)");
return true;
}
catch (Exception ex)
{
_g.Errs.Raise(ex);
return false;
}
}
This works correctly and populates moProductPrice with the data returned from PostRequest. However, it is async.
I've create a wrapper function thus:
private bool ReadProductPrice2()
{
Task<bool> oTask;
frmWaitForTaskCompletion frm;
try
{
oTask = ReadProductPrice();
frm = new frmWaitForTaskCompletion();
frm.WaitForTaskCompletion(oTask, "Waiting for product price from server...");
return true;
}
catch (Exception ex)
{
_g.Errs.Raise(ex);
return false;
}
}
This passes the Task<bool> returned from ReadProductPrice through to a form. The form contains a Label and a Timer, named lblMessage and tmr, containing the following code:
public partial class frmWaitForTaskCompletion : Form
{
private Task _task;
public frmWaitForTaskCompletion()
{
InitializeComponent();
}
public void WaitForTaskCompletion<TResult>(Task<TResult> oTask, string cMessage)
{
_task = oTask;
lblMessage.Text = cMessage;
this.ShowDialog();
return;
}
private void frmWaitForTaskCompletion_Load(object sender, EventArgs e)
{
tmr.Enabled = true;
}
private void tmr_Tick(object sender, EventArgs e)
{
if (_task.Status == TaskStatus.RanToCompletion)
this.Close();
}
}
The timer is set to an Interval of 1000 so that it shows for enough time for the user to recognise that a popup has occurred and to scan the message.
Ideally, I would like to replace the call to the wait form with this:
while (oTask.Status != TaskStatus.RanToCompletion) Thread.Sleep(100);
And I don't actually understand why this doesn't now work, but recognise that it doesn't; code never continues after this point, despite the fact that the wait form is effectively performing the same check.
In this way, I'm able to stop the await/async propogating up my call stack indefinitely; IMO should be the compiler's job to sort that out, not mine, and it signifcantly breaks the concept of encapsulation. I dislike the fact that I need to show a wait form for a short while, but in this context the user should be aware of the communication that is going on, so it's an ok solution.
You can try doing something like this
private void button2_Click(object sender, EventArgs e)
{
PostRequest(
"http://mywebsite.com/mypage.php",
"MagicToken",
"12345",
"Method",
"GetSomeData"
)
.ContinueWith(async request => {
var cResult = await request;
txt.Text = cResult.ToString();
})
.Wait();
}
I am building a telemetry simulator to send messages to an Azure EventHub (WinForm, .NET 5.0).
I use backgroundworkers for each telemetry device, the code below is the DoWork method of one of the devices. The app works fine when I output the messages to the console.
The problem occurs when I add the (commented out) EventHub code shown in the while loop below. There are two issues:
Backgroundworker.ReportProgress fails with: System.InvalidOperationException: 'This operation has already had OperationCompleted called on it and further calls are illegal.'
I also can no longer cancel the process (i.e. truckWorker.CancellationPending is always false)
If I uncomment the EventHub code, and comment out truckWorker.ReportProgress(i++), the app works and sends messages to the EventHub. Problem is I still can't cancel the operation and of course I loose my progress indicator.
private async void SendTruckMsg(object sender, DoWorkEventArgs e)
{
EventHubProducerClient evClient = (EventHubProducerClient)e.Argument;
List<TruckTelemetry> collection = virtualThingsController.GetTruckTelemetry();
int interval = virtualThingsController.GetTruckTelemetryInterval();
int i = 0; // count messages sent
while (!truckWorker.CancellationPending) // ===> can't cancel operation anymore
{
//using var eventBatch = await evClient.CreateBatchAsync(); // create batch
foreach (TruckTelemetry truckTelemetry in collection)
{
truckTelemetry.Timestamp = DateTime.Now;
string output = JsonConvert.SerializeObject(truckTelemetry);
//eventBatch.TryAdd(new EventData(Encoding.UTF8.GetBytes(output))); // add to batch
}
//await evClient.SendAsync(eventBatch); // send the batch
truckWorker.ReportProgress(i++);
Thread.Sleep(interval);
}
}
Sending messages could be synchronous but there is no simple 'send' method for the EventHubProducerClient.
Appreciate any help, thanks.
Before I touch on the background processing, there's a couple of things that I'm seeing in your snippet that look like they're likely to be a problem in your application:
It appears that you're using a List<TruckTelemetry> on multiple threads, one that adds items and the background operation that publishes them to Event Hubs. The list isn't thread-safe. I'd recommend moving to a ConcurrentQueue<T>, which is thread-safe and will help preserve the ordering of your telemetry.
Your snippet is ignoring the return of the TryAdd method of the EventDataBatch and assuming that every telemetry item in your loop was successfully added to the batch. This can lead to data loss, as you would be unaware if the batch was full and events were not able to be added.
Your snippet doesn't handle the corner case where a telemetry item is too large and could never be published to Event Hubs. Granted, if your telemetry items are tiny, this isn't a case that you're likely to encounter, but I'd recommend including it just to be safe.
With asynchronous operations, the typical approach to working in the background is to start a new Task that you can cancel or wait to complete when it makes sense for the application.
If I translate your general scenario, turning it into a task and addressing my feedback, your telemetry processing would look something like:
public static Task ProcessTruckTelemetry(EventHubProducerClient producer,
VirtualThingsController controller,
CancellationToken cancellationToken,
Action<int> progressCallback) =>
Task.Run(async () =>
{
var eventBatch = default(EventDataBatch);
var totalEventCount = 0;
var interval = controller.GetTruckTelemetryInterval();
// I'm assuming a change to ConcurrentQueue<T> for the
// the telemetry interactions via the controller.
var telemetryQueue = controller.GetTruckTelemetry();
while (!cancellationToken.IsCancellationRequested)
{
// In this example, we'll pump the telemetry queue as long
// long as there are any items in there. If you wanted to
// limit, you could also include a maximum count and stop there.
while ((!cancellationToken.IsCancellationRequested)
&& (telemetryQueue.TryPeek(out var telemetry))
{
// Create a batch if we don't currently have one.
eventBatch ??= (await producer.CreateBatchAsync().ConfigureAwait(false));
// Translate the telemetry data.
telemetry.Timestamp = DateTime.UtcNow;
var serializedTelemetry = JsonSerializer.Serialize(telemetry);
var eventData = new EventData(new BinaryData(serializedTelemetry));
// Attempt to add the event to the batch. If the batch is full,
// send it and clear state so that we know to create a new one.
if (!eventBatch.TryAdd(eventData))
{
// If there are no events in the batch, this event is
// too large to ever publish. We can't recover.
//
// An important note in this scenario is that we have
// already removed the telemetry from the queue. If we don't
// want to lose that, we should take action before throwing the
// exception to capture it.
if (eventBatch.Count == 0)
{
throw new Exception("There was an event too large to publish.");
}
await producer.SendAsync(eventBatch).ConfigureAwait(false);
totalEventCount += eventBatch.Count;
eventBatch.Dispose();
eventBatch = default;
}
}
else
{
telemetryQueue.TryDequeue(out _);
}
// Once we hit this point, there were no telemetry items left
// in the queue. Send any that are held in the event batch.
if ((eventBatch != default) && (eventBatch.Count > 0))
{
await producer.SendAsync(eventBatch).ConfigureAwait(false);
totalEventCount += eventBatch.Count;
eventBatch.Dispose();
eventBatch = default;
}
// Invoke the progress callback with the total count.
progressCallback(totalEventCount);
// Pause for the requested delay before attempting to
// pump the telemetry queue again.
try
{
await Task.Delay(interval, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Thrown if cancellation is requested while we're in the
// delay. This example is assuming that it isn't interesting
// to the application and swallows it.
}
}
}, cancellationToken);
Which your application would interact with using something similar to:
// This is your master shutdown signal. When you request
// cancellation on this token, all of your background tasks
// should terminate.
using var cancellationSource = new CancellationTokenSource();
var producer = GetProducerClient();
var virtualThingsController = new VirtualThingsController();
try
{
// Define a simple process callback.
Action<int> progressCallback = totalCount =>
Debug.WriteLine($"There have been a total of { totalCount } items published.");
// Start the background processing and capture the tasks. Once the
// call is made, telemetry is being processed in the backgorund until
// the cancellation token is signaled.
var backgroundTasks = new List<Task>();
backgroundTasks.Add
(
ProcessTruckTelemetry(
producer,
virtualThingsController,
cancellationSource.Token,
progressCallback)
);
backgroundTasks.Add
(
ProcessOtherTelemetry(
producer,
virtualThingsController,
cancellationSource.Token,
progressCallback)
);
// The application can do whatever it normally does now.
//
// << STUFF >>
//
// When the application is ready to stop, signal the cancellation
// token and wait for the tasks. We're not calling ConfigureAwait(false)
// here because your application is WinForms, which has some
// sensitivity to the syncrhonization context.
cancellationSource.Cancel();
await Task.WhenAll(backgroundTasks);
}
finally
{
await producer.CloseAsync();
}
I wrote this code on c#
public class SerialClass
{
SerialPort s;
public Serial()
{
InitSerialPort();
s.DataReceived += dataReciver;
}
private void dataReciver(object sender, SerialDataReceivedEventArgs e)
{
lock (obj)
{
while (s.BytesToRead >0)
{
var line = s.ReadLine();
if(line=="hello")
{
Thread.Sleep(500);
s.WriteLine("hello to you friend");
}
else //......
}
}
}
}
When i got "hello" from the serial I want to answer after 500 milliseconds "hello to you friend".
I heard so much , don't use sleep on you code..
What is the disadvantage here to use sleep? If more data will get on serialport so new event will enter to dataReciver because it will be open on secondery thread.
so what is the disadvantage and what is the better/best way to implement it without sleep?
I use lock because I want only 1 thread will be on this reading
If you've done it right, you shouldn't need the lock.
IMHO, you should avoid the DataReceived event altogether. Wrap SerialPort.BaseStream in a StreamReader, then loop in an async method to read. Regardless, I also would not put the delay, asynchronous or otherwise, in sequence with your reading. You should always be ready to read.
You didn't provide real code, so it's impossible to offer a real code solution, but here's how I'd have written the bit of code you posted:
public class Serial
{
SerialPort s;
public Serial()
{
InitSerialPort();
// Ignore returned task...constructors shouldn't wait. You could store
// the task in a class field, to provide a mechanism to observe the
// receiving state.
Task task = ReceiveLoopAsync();
}
private async Task ReceiveLoopAsync()
{
using (StreamWriter writer = new StreamWriter(s.BaseStream))
using (StreamReader reader = new StreamReader(s.BaseStream))
{
string line;
while ((line = reader.ReadLineAsync()) != null)
{
if (line == "hello")
{
// Ignore returned task...we don't really care when it finishes
Task task = RespondAsync(writer);
}
}
}
}
private async Task RespondAsync(StreamWriter writer)
{
await Task.Delay(500);
writer.WriteLine("hello to you friend");
}
}
I've left out niceties like exception handling and more robust handling of the tasks. But the above is the basic idea. Note that all receiving is done in a single loop, with no need for cross-thread synchronization.
I am trying to connect to wifi with the following code:
private static bool ConnectToWifi(string profileName, WlanClient.WlanInterface wlanIface, Wifi wifi, string profile)
{
try
{
wlanIface.SetProfile(Wlan.WlanProfileFlags.AllUser, profile, true);
}
catch (Exception e)
{
var ex = e;
return false;
};
// Task.Run()
wlanIface.Connect(Wlan.WlanConnectionMode.Profile, Wlan.Dot11BssType.Infrastructure, profileName);
Thread.Sleep(5000);
var status = wifi.ConnectionStatus;
var x = wlanIface.GetProfileXml(profileName);
if (status == WifiStatus.Disconnected)
{
return false;
}
return true;
}
I have kept a delay of 5000 ms to ensure the network is connected, but this is causing my UI to not show the loading icon when this code executes.
How I can make my UI also update at same time, instead of waiting for the connection?
You have two options:
(Both of which make it not possible to return a bool that indicates a successful connection without a bit more logic around it.)
Move your code to a separate thread (if the rest of it is thread-safe) and use the synchronous methods instead:
private static void ConnectToWifi(string profileName, WlanClient.WlanInterface wlanIface, Wifi wifi, string profile)
{
new Thread(()=>{
bool result = false;
try
{
wlanIface.SetProfile(Wlan.WlanProfileFlags.AllUser, profile, true);
wlanIface.ConnectSynchronously(Wlan.WlanConnectionMode.Profile, Wlan.Dot11BssType.Infrastructure, profileName, 5000);
var status = wifi.ConnectionStatus;
var x = wlanIface.GetProfileXml(profileName);
result = (status != WifiStatus.Disconnected);
}
catch (Exception e)
{
var ex = e;
}
finally
{
Dispatcher.BeginInvoke(new Action(()=>{WhateverYouDoWithYourResult(result);}));
}
}).Start();
}
Or subscribe to the WlanConnectionNotification (Not being able to connect might not be seen as a change, so you have to test that):
private static bool ConnectToWifi(string profileName, WlanClient.WlanInterface wlanIface, Wifi wifi, string profile)
{
try
{
wlanIface.WlanConnectionNotification += Interface_ConnectionStateChanged;
wlanIface.SetProfile(Wlan.WlanProfileFlags.AllUser, profile, true);
wlanIface.Connect(Wlan.WlanConnectionMode.Profile, Wlan.Dot11BssType.Infrastructure, profileName);
return true; //Just means the attempt was successful, not the connecting itself
}
catch (Exception e)
{
var ex = e;
return false;
}
}
private static void Interface_ConnectionStateChanged(Wlan.WlanNotificationData notifyData, Wlan.WlanConnectionNotificationData connNotifyData)
{
// Do something with that info, be careful - might not be the same thread as before.
}
I don't have access to a Wifi right now, so I haven't tested above code. It should work, but you better consider it pseudo-code instead of an actual ready-to-use solution.
Whenever you execute Thread.Sleep in the UI thread, you interrupt processing all UI messages, which makes your UI unresponsive. Hence, Thread.Sleep and any other long running operations should never be executed in the UI thread.
The solution is to execute these operations in a separate thread to allow the UI thread to continue UI operations. It is generally a good idea to let the UI thread do only UI operations.
In your case it means that the caller should execute the operation in a task:
private static bool ConnectToWifi(string profileName, WlanClient.WlanInterface wlanIface,
Wifi wifi, string profile, Action<bool> resultCallback, Dispatcher dispatcher)
{
//Your connect code
bool result;
if (status == WifiStatus.Disconnected)
{
result = false;
}
else
{
result = true;
}
dispatcher.BeginInvoke(() => resultCallback(result));
return result;
}
Another thing: Thread.Sleep is not a good idea in task, since you don't know which scheduler you are running on. You should use Task.Delay instead. In this case Thread.Sleep is generally not a good idea, since you just wait and hope your task is done in five seconds, which is not guaranteed. Also you might simply waste 5 seconds of the user's time in case it connects immediately. The better solution is to use a wait loop and check regularly if the connection is established. If you expect the connection to happen in rather short time, you can use SpinWait.SpinUntil (with a timeout of five seconds):
SpinWait.SpinUntil(() => wifi.ConnectionStatus == WifiStatus.Connected, 5000);
We currently have a naive RetryWrapper which retries a given func upon the occurrence of an exception:
public T Repeat<T, TException>(Func<T> work, TimeSpan retryInterval, int maxExecutionCount = 3) where TException : Exception
{
...
And for the retryInterval we are using the below logic to "wait" before the next attempt.
_stopwatch.Start();
while (_stopwatch.Elapsed <= retryInterval)
{
// do nothing but actuallky it does! lots of CPU usage specially if retryInterval is high
}
_stopwatch.Reset();
I don't particularly like this logic, also ideally I would prefer the retry logic NOT to happen on the main thread, can you think of a better way?
Note: I am happy to consider answers for .Net >= 3.5
So long as your method signature returns a T, the main thread will have to block until all retries are completed. However, you can reduce CPU by having the thread sleep instead of doing a manual reset event:
Thread.Sleep(retryInterval);
If you are willing to change your API, you can make it so that you don't block the main thread. For example, you could use an async method:
public async Task<T> RepeatAsync<T, TException>(Func<T> work, TimeSpan retryInterval, int maxExecutionCount = 3) where TException : Exception
{
for (var i = 0; i < maxExecutionCount; ++i)
{
try { return work(); }
catch (TException ex)
{
// allow the program to continue in this case
}
// this will use a system timer under the hood, so no thread is consumed while
// waiting
await Task.Delay(retryInterval);
}
}
This can be consumed synchronously with:
RepeatAsync<T, TException>(work, retryInterval).Result;
However, you can also start the task and then wait for it later:
var task = RepeatAsync<T, TException>(work, retryInterval);
// do other work here
// later, if you need the result, just do
var result = task.Result;
// or, if the current method is async:
var result = await task;
// alternatively, you could just schedule some code to run asynchronously
// when the task finishes:
task.ContinueWith(t => {
if (t.IsFaulted) { /* log t.Exception */ }
else { /* success case */ }
});
Consider using Transient Fault Handling Application Block
The Microsoft Enterprise Library Transient Fault Handling Application
Block lets developers make their applications more resilient by adding
robust transient fault handling logic. Transient faults are errors
that occur because of some temporary condition such as network
connectivity issues or service unavailability. Typically, if you retry
the operation that resulted in a transient error a short time later,
you find that the error has disappeared.
It is available as a NuGet package.
using Microsoft.Practices.TransientFaultHandling;
using Microsoft.Practices.EnterpriseLibrary.WindowsAzure.TransientFaultHandling;
...
// Define your retry strategy: retry 5 times, starting 1 second apart
// and adding 2 seconds to the interval each retry.
var retryStrategy = new Incremental(5, TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2));
// Define your retry policy using the retry strategy and the Windows Azure storage
// transient fault detection strategy.
var retryPolicy =
new RetryPolicy<StorageTransientErrorDetectionStrategy>(retryStrategy);
// Receive notifications about retries.
retryPolicy.Retrying += (sender, args) =>
{
// Log details of the retry.
var msg = String.Format("Retry - Count:{0}, Delay:{1}, Exception:{2}",
args.CurrentRetryCount, args.Delay, args.LastException);
Trace.WriteLine(msg, "Information");
};
try
{
// Do some work that may result in a transient fault.
retryPolicy.ExecuteAction(
() =>
{
// Your method goes here!
});
}
catch (Exception)
{
// All the retries failed.
}
How about using a timer instead of stopwatch?
For example:
TimeSpan retryInterval = new TimeSpan(0, 0, 5);
DateTime startTime;
DateTime retryTime;
Timer checkInterval = new Timer();
private void waitMethod()
{
checkInterval.Interval = 1000;
checkInterval.Tick += checkInterval_Tick;
startTime = DateTime.Now;
retryTime = startTime + retryInterval;
checkInterval.Start();
}
void checkInterval_Tick(object sender, EventArgs e)
{
if (DateTime.Now >= retryTime)
{
checkInterval.Stop();
// Retry Interval Elapsed
}
}