I have a requirement to connect to the server and collect data for processing.
Here is the Connect method
private readonly ServerComLibrary _vMyServer;// this is initialised in constructor
public ConnectToServer(string servername)
{
_vMyServer.connectToServerByName("ssl",servername);
}
_vMyServer has below events
onConnectSucceeded - I will collect data and copy it to excel file
onConnectFailed - just log the exception
Here is a connection success event
private void Handle_OnConnectSucceeded()
{
//collect data and create excel
}
Here is a Failed event
private void Handle_OnConnectFailed(int hr)
{
//log exception
}
Everything works fine!
But, now my requirement is to connect to multiple servers one by one
List<Server> servers = ConfigurationManager.GetSection("servers") as List<Server>;
var datacollectionTasks = new List<Task>();
foreach (var server in servers)
{
var data = Task.Run(() => ConnectToServer(server.serveraddress));
datacollectionTasks.Add(dataFix);
}
await Task.WhenAll(datacollectionTasks);
I want to start the second task only after first task of connecting to the server and creating excel generation compleats or connection fails.
How can I do it? I may use ContinueWith but not sure how to confirm if events fired and job completed.
my requirement is to connect to multiple servers one by one
Then I'm not sure why you're using Task.WhenAll.
How can I do it? I may use ContinueWith but not sure how to confirm if events fired and job completed.
In order to chain tasks, you need a task to chain onto. Specifically, change the events into a Task by using TaskCompletionSource<T>:
public static class ServerComLibraryExtensions
{
public static Task ConnectAsync(this ServerComLibrary #this, string protocol, string host)
{
var tcs = new TaskCompletionSource<object>();
Action onSucceeded = null;
Action<int> onFailed = null;
onSuccess = () =>
{
#this.OnConnectSucceeded -= onSucceeded;
#this.OnConnectFailed -= onFailed;
tcs.TrySetResult(null);
};
onFailed = hr =>
{
#this.OnConnectSucceeded -= onSucceeded;
#this.OnConnectFailed -= onFailed;
tcs.TrySetException(Marshal.GetExceptionForHR(hr));
};
#this.OnConnectSucceeded += onSucceeded;
#this.OnConnectFailed += onFailed;
#this.connectToServerByName(protocol, host);
return tcs.Task;
}
}
Now that the connect operation is represented as a Task instead of events, it can naturally be "chained" by using the await keyword:
List<Server> servers = ConfigurationManager.GetSection("servers") as List<Server>;
foreach (var server in servers)
{
await _vMyServer.ConnectAsync("ssl", server.serveraddress);
// collect data and create excel
}
Related
I have a windows service which is consuming a messaging system to fetch messages. I have also created a callback mechanism with the help of Timer class which helps me to check the message after some fixed time to fetch and process. Previously, the service is processing the message one by one. But I want after the message arrives the processing mechanism to execute in parallel. So if the first message arrived it should go for processing on one task and even if the processing is not finished for the first message still after the interval time configured using the callback method (callback is working now) next message should be picked and processed on a different task.
Below is my code:
Task.Factory.StartNew(() =>
{
Subsriber<Message> subsriber = new Subsriber<Message>()
{
Interval = 1000
};
subsriber.Callback(Process, m => m != null);
});
public static void Process(Message message)
{
if (message != null)
{
// Processing logic
}
else
{
}
}
But using the Task Factory I am not able to control the number of tasks in parallel so in my case I want to configure the number of tasks on which messages will run on the availability of the tasks?
Update:
Updated my above code to add multiple tasks
Below is the code:
private static void Main()
{
try
{
int taskCount = 5;
Task.Factory.StartNewAsync(() =>
{
Subscriber<Message> consumer = new
Subcriber<Message>()
{
Interval = 1000
};
consumer.CallBack(Process, msg => msg!=
null);
}, taskCount);
Console.ReadLine();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
public static void StartNewAsync(this TaskFactory
target, Action action, int taskCount)
{
var tasks = new Task[taskCount];
for (int i = 0; i < taskCount; i++)
{
tasks[i] = target.StartNew(action);
}
}
public static void Process(Message message)
{
if (message != null)
{
}
else
{ }
}
}
I think what your looking for will result in quite a large sample. I'm trying just to demonstrate how you would do this with ActionBlock<T>. There's still a lot of unknowns so I left the sample as skeleton you can build off. In the sample the ActionBlock will handle and process in parallel all your messages as they're received from your messaging system
public class Processor
{
private readonly IMessagingSystem _messagingSystem;
private readonly ActionBlock<Message> _handler;
private bool _pollForMessages;
public Processor(IMessagingSystem messagingSystem)
{
_messagingSystem = messagingSystem;
_handler = new ActionBlock<Message>(msg => Process(msg), new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = 5 //or any configured value
});
}
public async Task Start()
{
_pollForMessages = true;
while (_pollForMessages)
{
var msg = await _messagingSystem.ReceiveMessageAsync();
await _handler.SendAsync(msg);
}
}
public void Stop()
{
_pollForMessages = false;
}
private void Process(Message message)
{
//handle message
}
}
More Examples
And Ideas
Ok, sorry I'm short on time but here's the general idea/skeleton of what I was thinking as an alternative.
If I'm honest though I think the ActionBlock<T> is the better option as there's just so much done for you, with the only limit being that you can't dynamically scale the amount of work it will do it once, although I think the limit can be quite high. If you get into doing it this way you could have more control or just have a kind of dynamic amount of tasks running but you'll have to do a lot of things manually, e.g if you want to limit the amount of tasks running at a time, you'd have to implement a queueing system (something ActionBlock handles for you) and then maintain it. I guess it depends on how many messages you're receiving and how fast your process handles them.
You'll have to check it out and think of how it could apply to your direct use case as I think some of the details area a little sketchily implemented on my side around the concurrentbag idea.
So the idea behind what I've thrown together here is that you can start any number of tasks, or add to the tasks running or cancel tasks individually by using the collection.
The main thing I think is just making the method that the Callback runs fire off a thread that does the work, instead of subscribing within a separate thread.
I used Task.Factory.StartNew as you did, but stored the returned Task object in an object (TaskInfo) which also had it's CancellationTokenSource, it's Id (assigned externally) as properties, and then added that to a collection of TaskInfo which is a property on the class this is all a part of:
Updated - to avoid this being too confusing i've just updated the code that was here previously.
You'll have to update bits of it and fill in the blanks in places like with whatever you have for my HeartbeatController, and the few events that get called because they're beyond the scope of the question but the idea would be the same.
public class TaskContainer
{
private ConcurrentBag<TaskInfo> Tasks;
public TaskContainer(){
Tasks = new ConcurrentBag<TaskInfo>();
}
//entry point
//UPDATED
public void StartAndMonitor(int processorCount)
{
for (int i = 0; i <= processorCount; i++)
{
Processor task = new Processor(ProcessorId = i);
CreateProcessorTask(task);
}
this.IsRunning = true;
MonitorTasks();
}
private void CreateProcessorTask(Processor processor)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
Task taskInstance = Task.Factory.StartNew(
() => processor.Start(cancellationTokenSource.Token)
);
//bind status update event
processor.ProcessorStatusUpdated += ReportProcessorProcess;
Tasks.Add(new ProcessorInfo()
{
ProcessorId = processor.ProcessorId,
Task = taskInstance,
CancellationTokenSource = cancellationTokenSource
});
}
//this method gets called once but the HeartbeatController gets an action as a param that it then
//executes on a timer. I haven't included that but you get the idea
//This method also checks for tasks that have stopped and restarts them if the manifest call says they should be running.
//Will also start any new tasks included in the manifest and stop any that aren't included in the manifest.
internal void MonitorTasks()
{
HeartbeatController.Beat(() =>
{
HeartBeatHappened?.Invoke(this, null);
List<int> tasksToStart = new List<int>();
//this is an api call or whatever drives your config that says what tasks must be running.
var newManifest = this.GetManifest(Properties.Settings.Default.ResourceId);
//task Removed Check - If a Processor is removed from the task pool, cancel it if running and remove it from the Tasks List.
List<int> instanceIds = new List<int>();
newManifest.Processors.ForEach(x => instanceIds.Add(x.ProcessorId));
var removed = Tasks.Select(x => x.ProcessorId).ToList().Except(instanceIds).ToList();
if (removed.Count() > 0)
{
foreach (var extaskId in removed)
{
var task = Tasks.FirstOrDefault(x => x.ProcessorId == extaskId);
task.CancellationTokenSource?.Cancel();
}
}
foreach (var newtask in newManifest.Processors)
{
var oldtask = Tasks.FirstOrDefault(x => x.ProcessorId == newtask.ProcessorId);
//Existing task check
if (oldtask != null && oldtask.Task != null)
{
if (!oldtask.Task.IsCanceled && (oldtask.Task.IsCompleted || oldtask.Task.IsFaulted))
{
var ex = oldtask.Task.Exception;
tasksToStart.Add(oldtask.ProcessorId);
continue;
}
}
else //New task Check
tasksToStart.Add(newtask.ProcessorId);
}
foreach (var item in tasksToStart)
{
var taskToRemove = Tasks.FirstOrDefault(x => x.ProcessorId == item);
if (taskToRemove != null)
Tasks.Remove(taskToRemove);
var task = newManifest.Processors.FirstOrDefault(x => x.ProcessorId == item);
if (task != null)
{
CreateProcessorTask(task);
}
}
});
}
}
//UPDATED
public class Processor{
private int ProcessorId;
private Subsriber<Message> subsriber;
public Processor(int processorId) => ProcessorId = processorId;
public void Start(CancellationToken token)
{
Subsriber<Message> subsriber = new Subsriber<Message>()
{
Interval = 1000
};
subsriber.Callback(Process, m => m != null);
}
private void Process()
{
//do work
}
}
Hope this gives you an idea of how else you can approach your problem and that I didn't miss the point :).
Update
To use events to update progress or which tasks are processing, I'd extract them into their own class, which then has subscribe methods on it, and when creating a new instance of that class, assign the event to a handler in the parent class which can then update your UI or whatever you want it to do with that info.
So the content of Process() would look more like this:
Processor processor = new Processor();
Task task = Task.Factory.StartNew(() => processor.ProcessMessage(cancellationTokenSource.CancellationToken));
processor.StatusUpdated += ReportProcess;
I wrote a Client/Server async classes which works fine in the console. I created a WinForm project for the server which subscribes to an event thrown by the server when there is a .Pending() connection and writes some messages into a textbox - which causes a Cross-Thread exception. The exception does not surprise me, however I am looking for a way to invoke that event without causing this exception, without handling it on the GUI/Control with .InvokeRequired and .Invoke - if that is even possible?
The server is started like that:
Server server = new Server(PORT);
server.RunAsync();
in .RunAsync() i just iterate over the network devices and set them to listening and invoke an event that the server has started, this also writes into the GUI however without any issue.
public async Task RunAsync()
{
GetNetworkDevicesReady(Port);
await Task.Factory.StartNew(() =>
{
Parallel.ForEach(networkListeners, (listener) =>
{
Write.Info($"LISTENING ON {listener.LocalEndpoint}");
listener.Start();
});
});
IsRunning = true;
OnServerStarted?.Invoke(this, networkListeners.Where(l=>l.Active).ToList());
}
The code below is registered on the Form.Load event and does not cause a Cross-Thread exception when writing "SERVER STARTED" in the textbox.
server.OnServerStarted += (s, a) =>
{
consoleWindow1.Event("SERVER STARTED", $"{Environment.NewLine}\t{string.Join($"{Environment.NewLine}\t", a.Select(x=>x.LocalEndpoint))}");
consoleWindow1.Event("WAITING FOR PENDING CONNECTIONS");
server.WaitForConnectionsAsync();
};
And this is the code which runs indefinite until a cancellation token is triggered:
public async Task WaitForConnectionsAsync()
{
waitingForConnectionsToken = new CancellationTokenSource();
await (waitinfConnectionTaks=Task.Factory.StartNew(async () =>
{
while (!waitingForConnectionsToken.IsCancellationRequested)
{
foreach (var listener in networkListeners)
{
if (waitingForConnectionsToken.IsCancellationRequested) break;
if (!listener.Active)
{
continue;
}
if (listener.Pending())
{
try
{
TcpClient connection = await listener.AcceptTcpClientAsync();
//TODO: need to send it synchronised, since this causes a Cross-Thread when using WinForms
OnPendingConnection?.Invoke(this, connection);
}
catch (ObjectDisposedException x)
{
Write.Error(x.ToString());
}
}
}
}
}));
}
I know I can use the textbox .InvokeRequired and .Invoke on the GUI but I have the feeling that the server should throw the event in a way the GUI doesn't cause a Cross-Thread exception.
Is there a way to invoke the eventhandler in that "infinite task" without causing this exception?
Thanks to the comments and a good portion of sleep I solved my issue by changing the WaitForConnectionsAsync to the following code:
List<TcpClient> connections = new List<TcpClient>();
public async Task WaitForConnectionsAsync()
{
await (waitinfConnectionTaks = Task.Factory.StartNew(async () =>
{
//REMOVED LOOP
// while (!waitingForConnectionsToken.IsCancellationRequested)
{
foreach (var listener in networkListeners)
{
if (waitingForConnectionsToken.IsCancellationRequested) break;
if (!listener.Active)
{
continue;
}
if (listener.Pending())
{
try
{
TcpClient connection = await listener.AcceptTcpClientAsync();
//RETAIN CONNECTIONS IN A LIST
connections.Add(connection);
}
catch (ObjectDisposedException x)
{
Write.Error(x.ToString());
}
}
}
}
}));
//ITERATE OVER CONNECTIONS
foreach (var connection in connections)
{
//INVOKE EVENT
OnPendingConnection?.Invoke(this, connection);
}
//CLEAR THE LIST
connections.Clear();
//RESTART THE TASK
if(!waitingForConnectionsToken.IsCancellationRequested)
WaitForConnectionsAsync();
}
So Basically i am catching all pending connections into a list, once the work has completed, I run over the list, fire the event with each connection, clear the list and then start the task again. This code change doesn't throw the Cross-Thread exception anymore.
An improvement i could add now is to accept a collection in the event instead a single connection.
If you have any improvements or better practice suggestions, please let me know.
With the help of Google and community, I was able to build a nice set of methods allowing me to asynchronously call a function. This function is testing remote host properties, so it is idling most of the time. For this reason I would like to maximize the number of concurrent threads launched such that all calls can be processed in the minimum amount of time.
Here is the Code I have so far:
// Check remote host connectivity
public static class CheckRemoteHost
{
// Private Class members
private static bool AllDone = false;
private static object lockObj = new object();
private static List<string> IPs;
// Wrapper: manage async method <Ping>
public static List<string> Ping(HashSet<string> IP_Ports, int TimeoutInMS = 100)
{// async worker method: check remote host via <Ping>
// Locals
IPs = new List<string>();
// Perform remote host check
AllDone = false;
Ping_check(IP_Ports, TimeoutInMS);
while (!AllDone) { CommonLib.Utils.ApplicationWait(10, 10); }
// Finish
return IPs;
}
private static async void Ping_check(HashSet<string> IP_Ports, int timeout)
{
// Locals
var tasks = new List<Task>();
// Build task-set for parallel Ping checks
foreach (string host in IP_Ports)
{
var task = PingAndUpdateAsync(host, timeout);
tasks.Add(task);
}
// Start execution queue
await Task.WhenAll(tasks).ContinueWith(t =>
{
AllDone = true;
});
}
private static async Task PingAndUpdateAsync(string ip, int timeout)
{
// Locals
System.Net.NetworkInformation.Ping ping;
System.Net.NetworkInformation.PingReply reply;
try
{
ping = new System.Net.NetworkInformation.Ping();
reply = await ping.SendPingAsync(ip, timeout);
if(reply.Status == System.Net.NetworkInformation.IPStatus.Success)
{
lock (lockObj)
{
IPs.Add(ip);
}
}
}
catch
{
// do nothing
}
}
}// end public static class CheckRemoteHost
This code is tested quite extensively, and the code seems stable and reliably report live hosts. Having said that, I know that it only spawns 8 threads at a time (= number of logical core on my test machine).
The key portion of the code is this:
// Start execution queue
await Task.WhenAll(tasks).ContinueWith(t =>
{
AllDone = true;
});
This is where I would like to increase/ maximize the number of concurrently launched threads to something like 25 per core (remember the thread job is 99% idle).
So far, my thread concurrency research has brought up the explicit thread and Parallel.For approaches. However, these seem to have the same shortcoming of spawning no more than 8 threads.
Any help would be very much appreciated, so thank you very much in advance everyone for looking!
You're making your life hard with the code you have. It's got a lot of plumbing that isn't needed and you're sharing static fields that would cause your code to fail if you called Ping a second time while the first one is running.
You need to get rid of all of that stuff.
I'd suggest using Microsoft's Reactive Framework - just NuGet "System.Reactive" and add using System.Reactive.Linq; to your code. Then you can do this:
public static class CheckRemoteHost
{
public static IList<string> Ping(HashSet<string> IP_Ports, int TimeoutInMS = 100)
{
var query =
from host in IP_Ports.ToObservable()
from status in Observable.FromAsync(() => PingAsync(host, TimeoutInMS))
where status
select host;
return query.ToList().Wait();
}
private static async Task<bool> PingAsync(string ip, int timeout)
{
try
{
var ping = new System.Net.NetworkInformation.Ping();
var reply = await ping.SendPingAsync(ip, timeout);
return reply.Status == System.Net.NetworkInformation.IPStatus.Success;
}
catch
{
return false;
}
}
}
That's it. That's all of the code you need. It's automatically maximising the thread use to get the job done.
When I execute the following code:
public static async Task UploadFile(string serverPath, string pathToFile, string authToken)
{
serverPath = #"C:\_Series\S1\The 100 S01E03.mp4";
var client = new WebClient();
var uri = new Uri($"http://localhost:50424/api/File/Upload?serverPath={WebUtility.UrlEncode(serverPath)}");
client.UploadProgressChanged += UploadProgressChanged;
client.UploadFileCompleted += UploadCompletedCallback;
//client.UploadFileAsync(uri, "POST", pathToFile);
client.UploadFile(uri, "POST", pathToFile);
}
I get the exception:
System.Net.WebException: 'The remote server returned an error: (404)
Not Found.'
I'm not too worried about the 404, I'm busy tracing down why the WebClient can't find it, but my big concern is that if I call UploadFileAsync with the same uri, the method just executes as if nothing is wrong.
The only indication that something is wrong is that neither of the two event handlers is invoked. I strongly suspect that I don't get an exception because the async call is not async/await but event based, but then I would expect some kind of event or property that indicates an exception has occurred.
How is one supposed to use code that hides errors like this, especially network errors which are relatively more common, in production?
Why no error notification for UploadFileAsync with WebClient?
Citing WebClient.UploadFileAsync Method (Uri, String, String) Remarks
The file is sent asynchronously using thread resources that are automatically allocated from the thread pool. To receive notification when the file upload completes, add an event handler to the UploadFileCompleted event.
Emphasis mine.
You get no errors because it is being executed on another thread so as not to block the current thread. To see the error you can access it in the stated event handler via the UploadFileCompletedEventArgs.Exception.
I was curious as to why using WebClient and not HttpClient which is already primarily async, but then my assumption was because of the upload progress.
I would suggest wrapping the WebClient call with event handlers in a Task using a TaskCompletionSource to take advantage of TAP.
The following is similar to the examples provided here How to: Wrap EAP Patterns in a Task
public static async Task UploadFileAsync(string serverPath, string pathToFile, string authToken, IProgress<int> progress = null) {
serverPath = #"C:\_Series\S1\The 100 S01E03.mp4";
using (var client = new WebClient()) {
// Wrap Event-Based Asynchronous Pattern (EAP) operations
// as one task by using a TaskCompletionSource<TResult>.
var task = client.createUploadFileTask(progress);
var uri = new Uri($"http://localhost:50424/api/File/Upload?serverPath={WebUtility.UrlEncode(serverPath)}");
client.UploadFileAsync(uri, "POST", pathToFile);
//wait here while the file uploads
await task;
}
}
Where createUploadFileTask is a custom extension method used to wrap the Event-Based Asynchronous Pattern (EAP) operations of the WebClient as one task by using a TaskCompletionSource<TResult>.
private static Task createTask(this WebClient client, IProgress<int> progress = null) {
var tcs = new TaskCompletionSource<object>();
#region callbacks
// Specifiy the callback for UploadProgressChanged event
// so it can be tracked and relayed through `IProgress<T>`
// if one is provided
UploadProgressChangedEventHandler uploadProgressChanged = null;
if (progress != null) {
uploadProgressChanged = (sender, args) => progress.Report(args.ProgressPercentage);
client.UploadProgressChanged += uploadProgressChanged;
}
// Specify the callback for the UploadFileCompleted
// event that will be raised by this WebClient instance.
UploadFileCompletedEventHandler uploadCompletedCallback = null;
uploadCompletedCallback = (sender, args) => {
// unsubscribing from events after asynchronous
// events have completed
client.UploadFileCompleted -= uploadCompletedCallback;
if (progress != null)
client.UploadProgressChanged -= uploadProgressChanged;
if (args.Cancelled) {
tcs.TrySetCanceled();
return;
} else if (args.Error != null) {
// Pass through to the underlying Task
// any exceptions thrown by the WebClient
// during the asynchronous operation.
tcs.TrySetException(args.Error);
return;
} else
//since no result object is actually expected
//just set it to null to allow task to complete
tcs.TrySetResult(null);
};
client.UploadFileCompleted += uploadCompletedCallback;
#endregion
// Return the underlying Task. The client code
// waits on the task to complete, and handles exceptions
// in the try-catch block there.
return tcs.Task;
}
Going one step further and creating another extension method to wrap the upload file to make it await able...
public static Task PostFileAsync(this WebClient client, Uri address, string fileName, IProgress<int> progress = null) {
var task = client.createUploadFileTask(progress);
client.UploadFileAsync(address, "POST", fileName);//this method does not block the calling thread.
return task;
}
Allowed your UploadFile to be refactored to
public static async Task UploadFileAsync(string serverPath, string pathToFile, string authToken, IProgress<int> progress = null) {
using (var client = new WebClient()) {
var uri = new Uri($"http://localhost:50424/api/File/Upload?serverPath={WebUtility.UrlEncode(serverPath)}");
await client.PostFileAsync(uri, pathToFile, progress);
}
}
This now allow you to call the upload asynchronously and even keep track of the progress with your very own Progress Reporting (Optional)
For example if in an XAML based platform
public class UploadProgressViewModel : INotifyPropertyChanged, IProgress<int> {
public int Percentage {
get {
//...return value
}
set {
//...set value and notify change
}
}
public void Report(int value) {
Percentage = value;
}
}
Or using the out of the box Progress<T> Class
So now you should be able to upload the file without blocking the thread and still be able to await it, get progress notifications, and handle exceptions, provided you have a try/catch in place.
I was wondering the best way to get round this issue.
I have created a Windows Service that connects to a mailbox, processes the emails, then cleans up after itself, waits a certain amount of time and repeats.
protected override void OnStart(string[] args)
{
this._mainTask = new Task(this.Poll, this._cancellationToken.Token, TaskCreationOptions.LongRunning);
this._mainTask.Start();
}
private void Poll()
{
CancellationToken cancellation = this._cancellationToken.Token;
TimeSpan interval = TimeSpan.Zero;
while (!cancellation.WaitHandle.WaitOne(interval))
{
using (IImapClient emailClient = new S22ImapClient())
{
ImapClientSettings chatSettings = ...;
emailClient.Connect(chatSettings); // CAN SOMETIMES HANG HERE
// SOME WORK DONE HERE
}
interval = this._waitAfterSuccessInterval;
// check the cancellation state.
if (cancellation.IsCancellationRequested)
{
break;
}
}
}
Now I am using a 3rd party IMAP client "S22.Imap". When I create the email client object on occasion it will hang on creation as it is attempting to login. This in turn will hang my Windows Service indefinitely.
public class S22ImapClient : IImapClient
{
private ImapClient _client;
public void Connect(ImapClientSettings imapClientSettings)
{
this._client = new ImapClient(
imapClientSettings.Host,
imapClientSettings.Port,
imapClientSettings.EmailAddress,
imapClientSettings.Password,
AuthMethod.Login,
true);
}
}
How would I change the "S22ImapClient.Connect()" call to, behind the covers, use some method to attempt to connect for a set amount of time, then abort if it has not been able to?
The solution to this will also be used for anything that I need to do with the mail client, for example "GetMessage()", "DeleteMessage()" etc
You could use a cancellation token source and give it a time to cancel after in the event that it hangs too long. Otherwise you would just have to extend the third party class and implement an async version of the Connect method. This is untested but should give you the basic idea.
private void Poll()
{
CancellationTokenSource source = new CancellationTokenSource();
TimeSpan interval = TimeSpan.Zero;
while (!source.Token.WaitHandle.WaitOne(interval))
{
using (IImapClient emailClient = new S22ImapClient())
{
ImapClientSettings chatSettings = ...;
var task = Task.Run(() =>
{
source.CancelAfter(TimeSpan.FromSeconds(5));
emailClient.Connect(chatSettings); // CAN SOMETIMES HANG HERE
}, source.Token);
// SOME WORK DONE HERE
}
interval = this._waitAfterSuccessInterval;
// check the cancellation state.
if (source.IsCancellationRequested)
{
break;
}
}
}
I decided to stop using the S22.Imap email client for this particular problem, and use another 3rd party component, ActiveUp MailSystem, as it includes async calls out the box.
This way I can do code like this:
IAsyncResult connectResult = this._client.BeginConnectSsl(imapClientSettings.Host, imapClientSettings.Port, null);
if (!connectResult.AsyncWaitHandle.WaitOne(this._connectionTimeout))
{
throw new EmailTimeoutException(this._connectionTimeout);
}