RabbitMQ. Best practice for waiting for next message - c#

I have a receiver method in an ASP.NET Core web app:
public void ReceiveMessage()
{
using (var connection = CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: "QueueName",durable: false,exclusive: false,autoDelete: false,arguments: null);
channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
//Do something
channel.BasicAck(deliveryTag: ea.DeliveryTag,multiple: false);
};
channel.BasicConsume(queue: "QueueName",autoAck: false,consumer: consumer);
//BAD!!!
while (true)
{
}
}
}
You'll notice I am using while(true) which smells bad. I basically need to keep this method alive and wonder how have other people done it?
This method should stay alive all the time and automatically process messages 1 by 1

You can create a Hosted service and consume your message inside it. This Hosted service is always alive and can receive all the messages.
public class ProductTopicConsumerService : ConsumerBase, IHostedService
{
public ProductTopicConsumerService(ConnectionFactory connectionFactory)
: base(connectionFactory, ExchangeTypes.topic)
{
try
{
Consume<ProductCreatedIntegrationEvent>();
}
catch (Exception ex)
{
Console.WriteLine($"Error => {ex?.Message}");
}
}
protected override string Exchange => "ProductExchangeTopic5";
protected override string Queue => "Product.Updated5";
protected override string AppId => "ProductCreatedConsumer";
protected override string QueueAndExchangeRoutingKey => "Product.*";
public virtual Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public virtual Task StopAsync(CancellationToken cancellationToken)
{
Dispose();
return Task.CompletedTask;
}
}
and ConsumerBase could be like this :
public abstract class ConsumerBase : RabbitMqClientBase
{
public ConsumerBase(ConnectionFactory connectionFactory, ExchangeTypes exchangeType)
: base(connectionFactory, exchangeType,false)
{
}
protected void Consume<TMessage>()
{
var consumer = new AsyncEventingBasicConsumer(Channel);
consumer.Received += OnEventReceived<TMessage>;
Channel.BasicConsume(queue: RefinedQueueName, autoAck: false, consumer: consumer);
}
private Task OnEventReceived<TMessage>(object sender, BasicDeliverEventArgs #event)
{
try
{
var body = Encoding.UTF8.GetString(#event.Body.ToArray());
var message = JsonConvert.DeserializeObject<TMessage>(body);
ReceiveAction(message);
Channel.BasicAck(#event.DeliveryTag, false);
return Task.CompletedTask;
}
catch (Exception ex)
{
Console.WriteLine($"Error occurred in OnEventReceived. Message: {ex?.Message}");
Channel.BasicNack(#event.DeliveryTag, false, true);
throw;
}
finally
{
}
}
protected virtual void ReceiveAction<TMessage>(TMessage message)
{
}
}
and finally, rabbitMQBase could be something like this :
public abstract class RabbitMqClientBase : IDisposable
{
protected const string VirtualHost = "MQ";
protected abstract string Exchange { get; }
protected abstract string Queue { get; }
protected abstract string AppId { get; }
protected abstract string QueueAndExchangeRoutingKey { get; }
protected IModel Channel { get; private set; }
private IConnection _connection;
private readonly ConnectionFactory _connectionFactory;
private readonly ExchangeTypes _exchangeType;
private readonly bool _isPublisher;
protected RabbitMqClientBase(ConnectionFactory connectionFactory, ExchangeTypes exchangeType, bool isPublisher)
{
_connectionFactory = connectionFactory;
this._exchangeType = exchangeType;
this._isPublisher = isPublisher;
ConnectToRabbitMq();
}
protected internal string RefinedExchangeName => $"{VirtualHost}.{Exchange}";
protected internal string RefinedQueueName => $"{VirtualHost}.{Queue}";
protected internal string RefinedRoutingKey => $"{VirtualHost}.{QueueAndExchangeRoutingKey}";
private void ConnectToRabbitMq()
{
if (_connection == null || _connection.IsOpen == false)
{
_connection = _connectionFactory.CreateConnection();
}
if (Channel == null || Channel.IsOpen == false)
{
Channel = _connection.CreateModel();
Channel.ExchangeDeclare(exchange: RefinedExchangeName, type: _exchangeType.ToString(), durable: true, autoDelete: false);
if (!_isPublisher)
{
Channel.QueueDeclare(queue: RefinedQueueName, durable: true, exclusive: false, autoDelete: false);
Channel.QueueBind(queue: RefinedQueueName, exchange: RefinedExchangeName, routingKey: RefinedRoutingKey);
}
}
}
public void Dispose()
{
Channel?.Close();
Channel?.Dispose();
Channel = null;
_connection?.Close();
_connection?.Dispose();
_connection = null;
}
}
and this is Publisher|Prodcuer class for RabbitMQ :
public interface IRabbitMqProducer<in T>
{
PublishResult Publish(T #event);
}
public abstract class ProducerBase<T> : RabbitMqClientBase, IRabbitMqProducer<T>
{
protected ProducerBase(ConnectionFactory connectionFactory, ExchangeTypes exchangeType)
: base(connectionFactory, exchangeType, true) { }
public PublishResult Publish(T #event)
{
var result = new PublishResult();
try
{
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(#event));
var properties = Channel.CreateBasicProperties();
properties.AppId = AppId;
properties.ContentType = "application/json";
properties.DeliveryMode = 2; //persist mode
properties.Timestamp = new AmqpTimestamp(DateTimeOffset.UtcNow.ToUnixTimeSeconds());
Channel.BasicPublish(exchange: RefinedExchangeName, routingKey: RefinedRoutingKey, basicProperties: properties, body: body);
result.SetSuccess();
}
catch (Exception ex)
{
result.SetError(ex);
}
return result;
}
}
and for publishing your publisher should be like this:
public class ProductTopicProducerService : ProducerBase<ProductCreatedIntegrationEvent>
{
public ProductTopicProducerService(ConnectionFactory connectionFactory)
: base(connectionFactory,ExchangeTypes.topic)
{
}
protected override string Exchange => "ProductExchangeTopic5";
protected override string Queue => "";
protected override string AppId => "ProductCreatedConsumer";
protected override string QueueAndExchangeRoutingKey => "Product.*";
}
and for publish a new message follow the below code:
var result = _producer.Publish(#event);
if (!result.IsSuccess)
{
Console.WriteLine($"Error => {result.Description}");
}
Offsource you can simplify your consumer. but this is my experience to create a base class for rabbitMQBase and now both Publisher and consumer could use it.

Take a look at the OWIN package - available in NuGet: Microsoft.Owin.Hosting
When you use it you are self-hosting the web service and your app would start with a call to:
WebApp.Start(queryServiceUrl, Startup);
Where "Startup" is a method to do all your initialization. Your app/service stays running and will accept queries on the specified URL.

Without a loop, the instant after you call channel.BasicConsume, the whole thing (connection/channel) will go out of scope and be immediately disposed/destroyed via the using statement. So, without the loop, your consumer doesn't actually consume anything.
To ensure the consumer operates, you need to have an infinite loop, with appropriate logic to exit when you shut down the program. This is an unfortunate design of the .NET library for RabbitMQ, but it is what it is.
while (_isRunning & channel.IsOpen) {
Thread.Sleep(1);
// Other application logic here; e.g. periodically break out of the
// loop to prevent unacknowledged messages from accumulating in the system
// (if you don't, random effects will guarantee that they eventually build up)
}

Related

Hangfire not always execute retries tasks code and sometimes randomly marked them as succeeded

.Net Core (3.0.0), Hangfire.AspNetCore (1.7.7), Hangfire.SqlServer(1.7.7)
Case 1
I faced with an issue that sometimes hangfire do nothing(no code execution) but marked tasks as succeeded. Cannot find a pattern it happens randomly but pretty often 50/50.
//Entry point is .Net Core API
[Route("pull-daily")]
[HttpPost]
public IActionResult PullDaily([FromQuery] int hour, int minutes)
{
paymentsService.PullDaily(hour, minutes);
return Ok();
}
internal class PaymentsService : IPaymentsService
{
private readonly IPaymentsProvider _provider;
private readonly IJobManager _jobManager;
public PaymentsService(IPaymentsProvider provider, IJobManager jobManager)
{
_provider = provider;
_jobManager = jobManager;
}
public void PullDaily(int hour, int minute)
{
_jobManager.AddRecurring(
() => _provider.Pull(),
() => Cron.Daily(hour, minute));
}
}
internal class PaymentsProvider : IPaymentsProvider
{
//...
public void Pull()
{
throw new Exception("TEST EX " + DateTime.Now.ToLongTimeString());
}
//...
}
public class JobManager : IJobManager
{
public void AddRecurring(Expression<Action> action, Func<string> cronExpression)
{
handle(() => RecurringJob.AddOrUpdate(action, cronExpression));
}
private void handle(Action action)
{
try
{
action.Invoke();
}
catch (Exception e)
{
//...
}
}
}
Case 2
I've find out that if I simplified my code to couple lines there is another issue appears Hangfire still sometimes do nothing(no code execution) just marked attempts as failed with the error Hangfire.Storage.InvocationData.DeserializeJob Value cannot be null...
public class JobManager : IJobManager
{
public void Test()
{
throw new Exception("TEST EX " + DateTime.Now.ToLongTimeString());
}
public void AddRecurring(Expression<Action> action, Func<string> cronExpression)
{
RecurringJob.AddOrUpdate(() => Test(), cronExpression);
}
}
Startup.cs
//...
// Add Hangfire services.
services.AddHangfire(config => config
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
UsePageLocksOnDequeue = true,
DisableGlobalLocks = true
}));
// Add the processing server as IHostedService
services.AddHangfireServer();
//...
Please guys, help me find out what is wrong here.

How to prepare a console application that pushes and pops into a redis queue?

I need to prepare a console application with 3 buttons, one that adds elements to a Redis queue, one that pops elements out of it and one that displays elements in the queue. I am new to C# and Redis. Can anyone help me with this, or provide me some resources.
I have coded up the connection to the Redis DB and am able to set and get variables.
using System;
using StackExchange.Redis;
namespace RedisConsoleApp1
{
class Program
{
static void Main(string[] args)
{
ConnectionMultiplexer redisCon = ConnectionMultiplexer.Connect("localhost");
IDatabase db = redisCon.GetDatabase();
//db.Lis
db.StringSet("foo", "dog");
string val = db.StringGet("foo");
Console.WriteLine("output is {0}", val);
Console.ReadKey();
}
}
}
Write a channel subscriber like that
public class RedisHostingRunner : HostedService
{
private readonly IServiceProvider _serviceProvider;
IRedisSubscriber _subscriber;
public RedisHostingRunner(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_subscriber = _serviceProvider.GetRequiredService<RedisSubscriber>();
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
//while (!cancellationToken.IsCancellationRequested)
//{
_subscriber.SubScribeChannel();
//await Task.Delay(TimeSpan.FromSeconds(60), cancellationToken);
//}
}
public Task ShutdownAsync(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
}
And in your subscriber set a message handler
public void SubScribeChannel()
{
_logger.LogInformation("!SubScribeChannel started!!");
string channelName = _config.ActiveChannelName;
var pubSub = _connectionMultiplexer.GetSubscriber();
try
{
pubSub.Subscribe(channelName, async (channel, message) => await MessageActionAsync(message, channel));
}
catch(Exception ex)
{
_logger.LogInformation(String.Format("!error: {0}", ex.Message));
}
Debug.WriteLine("EOF");
}
In your handler do your job
private async Task MessageActionAsync(RedisValue message, string channel)
{
try
{
Transformer t = new Transformer(_logger);
_logger.LogInformation(String.Format("!SubScribeChannel message received on message!! channel: {0}, message: {1}", channel, message));
string transformedMessage = Transformer.TransformJsonStringData2Message(message);
List<Document> documents = Transformer.Deserialize<List<Document>>(transformedMessage);
await MergeToMongoDb(documents, channel);
_logger.LogInformation("!Merged");
}
catch (Exception ex)
{
_logger.LogInformation(String.Format("!error: {0}", ex.Message));
}
}

Rabbit MQ handle cancellation token

I am following this article (http://www.jarloo.com/listening-to-rabbitmq-events/) for consuming the messages on a console application, currently i am concerned about what happens when a user pressing CTRL + C and quits the application.
As a minimum i would prefer it complete processing the current message and ack before exiting the program. I am confused how can i implement this code as i am new to RabbitMQ.
I understand that channel.BasicConsume(queueName, true, consumer); is blocking thread.
Any help will be appreciated.
Here is what I managed to achieve but not sure if it is best way around or there is an improvement. I did not use the cancellationToken as yet not sure if we can use it.
On my console i get the expected result (see screenshot)
public abstract class QueueConnection : IDisposable
{
internal IConnection _connection;
internal IModel _model;
internal IBasicProperties _properties;
internal RabbitMQSettings _settings;
internal protected object _modelLock = new Object();
public QueueConnection(IOptions<RabbitMQSettings> queueConfig)
{
_settings = queueConfig.Value;
}
internal bool CreateModel(string queueName)
{
if (string.IsNullOrEmpty(queueName))
{
throw new ArgumentException("The queue name has to be specified before.");
}
lock (_modelLock)
{
if (!IsConnected) Connect();
if (_model == null || _model.IsClosed)
{
_model = _connection.CreateModel();
// When AutoClose is true, the last channel to close will also cause the connection to close.
// If it is set to true before any channel is created, the connection will close then and there.
_connection.AutoClose = true;
// Configure the Quality of service for the model. Below is how what each setting means.
// BasicQos(0="Dont send me a new message untill I’ve finshed", 1= "Send me one message at a time", false ="Apply to this Model only")
_model.BasicQos(0, 50, false);
const bool durable = true, queueAutoDelete = false, exclusive = false;
_model.QueueDeclare(queueName, durable, exclusive, queueAutoDelete, null);
_properties = RabbitMQProperties.CreateDefaultProperties(_model);
}
}
return true;
}
public void Connect()
{
var connectionFactory = new ConnectionFactory
{
HostName = _settings.HostName,
UserName = _settings.UserName,
Password = _settings.Password,
};
if (_settings.Port.HasValue) connectionFactory.Port = _settings.Port.Value;
if (_settings.Heartbeat.HasValue) connectionFactory.RequestedHeartbeat = _settings.Heartbeat.Value;
if (!string.IsNullOrEmpty(_settings.VirtualHost)) connectionFactory.VirtualHost = _settings.VirtualHost;
_connection = connectionFactory.CreateConnection();
}
public bool IsConnected
{
get { return _connection != null && _connection.IsOpen; }
}
public object GetConnection()
{
return _connection;
}
public void Disconnect()
{
if (_connection != null) _connection.Dispose();
}
void IDisposable.Dispose()
{
Disconnect();
}
}
QueueConsumer Class
public class QueueConsumer : QueueConnection, IQueueConsumer
{
private EventingBasicConsumer consumer;
public QueueConsumer(IOptions<RabbitMQSettings> queueConfig)
:base(queueConfig) {}
public void ReadFromQueue(Action<string, ulong> onDequeue, Action<Exception, ulong> onError)
{
ReadFromQueue(onDequeue, onError, _settings.QueueName);
}
public void ReadFromQueue(Action<string, ulong> onDequeue, Action<Exception, ulong> onError, string queueName)
{
CreateModel(queueName);
consumer = new EventingBasicConsumer(_model);
// Receive the messages
consumer.Received += (o, e) =>
{
try
{
var queueMessage = Encoding.UTF8.GetString(e.Body);
onDequeue.Invoke(queueMessage, e.DeliveryTag);
}
catch (Exception ex)
{
onError.Invoke(ex, e.DeliveryTag);
}
};
// if the consumer shutdown reconnects to rabbitmq and begin reading from the queue again.
consumer.Shutdown += (o, e) =>
{
CreateModel(queueName);
ReadFromQueue(onDequeue, onError, queueName);
};
_model.BasicConsume(queueName, false, consumer);
}
public void AcknowledgeMessage(ulong deliveryTag)
{
if (!IsConnected) Connect();
CreateModel(_settings.QueueName);
_model.BasicAck(deliveryTag, false);
}
public void StopListening()
{
_model.BasicCancel(consumer.ConsumerTag);
}
}
Main Class
static ManualResetEvent _quitEvent = new ManualResetEvent(false);
public static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
Console.WriteLine($"[{DateTime.UtcNow.ToString("dd/MM/yyyy HH:mm:ss")}] -> Worker role started");
var listener = serviceProvider.GetService<IMessageProcessor>();
Console.CancelKeyPress += (sender, eArgs) =>
{
listener.OnStop();
Console.WriteLine($"[{DateTime.UtcNow.ToString("dd/MM/yyyy HH:mm:ss")}] -> Worker role finished");
_quitEvent.Set();
eArgs.Cancel = true;
};
_quitEvent.WaitOne();
}
private static IConfigurationRoot GetConfiguration()
{
// Build appsetting.json configuration
var environment = Environment.GetEnvironmentVariable("Environment");
return new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{environment}.json", optional: true)
.AddEnvironmentVariables().Build();
}
private static void ConfigureServices(IServiceCollection services)
{
IConfigurationRoot configuration = GetConfiguration();
services.AddSingleton<IConfigurationRoot>(configuration);
// Support typed options
services.AddOptions();
services.Configure<RabbitMQSettings>(configuration.GetSection("RabbitMQConfig"));
services.AddSingleton<IQueueConsumer, QueueConsumer>();
services.AddScoped<IMessageProcessor, MessageProcessor>();
}
}
For the issue you are trying to get around only thing I can think of is treat this as a transaction.. Only when you get the message, process it completely and send the Ack back consider the transaction as completed.
If you process the message first and some one terminates the application before the Ack it will get queued again and when we restart the application it will get processed again.
Against that if you Ack first and then try to process the message and some one terminates the application you will lose the message.
So looks like considering the whole process as transaction will make it work or the other option is to take care of same message getting processed again.

Clean up for queues and connections - console app hangs on exit

Using RabbitMQ in a very simple console app I'm running into a problem where the console app isn't quiting at the end of execution and I think it might be because I'm hanging on to something in RabbitMQ. So what exactly do I need to dispose of? I have a class Queue class that looks something like this:
public class ManagementQueue : IDisposable
{
private IConnection connection;
private IModel channel;
private string privateQueueName;
private EventingBasicConsumer consumer;
public event EventHandler SomeEvents;
public ManagementQueue()
{
var factory = new ConnectionFactory()
{
HostName = Properties.Settings.Default.RabbitMQServer,
UserName = Properties.Settings.Default.RabbitMQUser,
Password = Properties.Settings.Default.RabbitMQPassword
};
connection = factory.CreateConnection();
channel = connection.CreateModel();
privateQueueName = channel.QueueDeclare().QueueName;
channel.QueueDeclare(queue: Properties.Settings.Default.ManagementQueueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.ExchangeDeclare(Properties.Settings.Default.BroadcastExchange, "fanout");
channel.ExchangeDeclare(Properties.Settings.Default.DirectExchange, "direct");
channel.QueueBind(privateQueueName,
Properties.Settings.Default.DirectExchange,
privateQueueName);
channel.QueueBind(Properties.Settings.Default.ManagementQueueName,
Properties.Settings.Default.DirectExchange,
"fromManager");
channel.BasicQos(0, 1, false);
}
public void StartListening()
{
consumer = new EventingBasicConsumer(channel);
consumer.Received += ReceivedHandler;
channel.BasicConsume(queue: privateQueueName,
noAck: true,
consumer: consumer);
}
public void StopListening()
{
consumer.Received -= ReceivedHandler;
}
private void ReceivedHandler(object model, BasicDeliverEventArgs ea)
{
// Do some stuff...
}
public void SomeFunction()
{
// send out some message...
var props = channel.CreateBasicProperties();
props.ReplyTo = privateQueueName;
channel.BasicPublish(exchange: Properties.Settings.Default.BroadcastExchange,
routingKey: "",
basicProperties: props,
body: null);
}
public void Dispose()
{
if (channel != null)
{
channel.Dispose();
channel = null;
}
if (connection != null)
{
connection.Dispose();
connection = null;
}
}
}
And in the console I have something like:
static void Main(string[] args)
{
if (!args.Any())
{
// This works fine exiting here
return;
}
var queue = new ManagementQueue();
queue.SomeEvents += (o,e) => { // do something };
queue.StartListening();
queue.SomeFunction();
while (!somecondition)
{
// do some other stuff
}
queue.StopListening();
queue.Dispose();
return; // app should quit here, but doesn't???
}
I'm suspicious that it might be something going on with the consumer, but there doesn't appear to be any methods to dispose, close or disconnect it. No where am I explicitly creating any new threads or any tight loops (other than the while loop, but that does exit when the condition evaluates to false).
This is a pretty old post, but it looks like RabbitMQ has had issues with calling Dispose on IModel. You may want to just try disposing of the connection and seeing if this resolves the issue.
Ref1: http://markmail.org/message/tqyiezrmixagtwa4#query:+page:1+mid:mhl5iw2gylwznd5o+state:results
Ref2: http://rabbitmq.1065348.n5.nabble.com/IModel-Dispose-locks-if-called-during-ConnectionShutdown-event-tc22104.html
Proposed Fix:
The suggestion is to not call dispose on the IModel instance during the
ConnectionShutdown event. I had to reword my code to track if it is being
called in a connectionshutdown or not to know when to call dispose.

Check if a async call to service is still alive

I have an async service
The service contract defined like:
[ServiceBehavior(InstanceContextMode = InstanceContext.PerCall]
Myservice
My client is defined like:
MyServiceClient task= null;
InstanceContext instanceContext = new InstanceContext(this);
task = new MyServiceClient(instanceContext);
task.MyMethod();
And the client class implements the call back methods (finish, progress etc...).
It's works fine, but if I call to the method, and she start running on the server and I shut down the server,I can't know the status of my call, and the client still think that the methods still running.
So, how can I check if this call is still running?
Thanks for helpers :)
Edit:
CallBack Interface:
public interface IServiceCallback
{
[OperationContract(IsOneWay=true)]
void NotifyFinished();
[OperationContract(IsOneWay=true)]
void NotifyProgress(int x);
[OperationContract(IsOneWay=true)]
void NotifyFailed(Exception exception);
}
Service Interface:
[ServiceContract(CallbackContract = typeof (IServiceCallback)]
public interface IAsyncService
{
[OperationContract(IsOneWay=true)]
void AsyncRunning();
}
Service Class:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class AsyncService : IAsyncService
{
private IServiceCallback ServiceCallback {get; set;}
public void AsyncRunningProxy ()
{
for(int x=0; x<100 ; x++)
{
AsyncService.NotifyProgress(x);
}
}
private void EndMethod(IAsyncResult res)
{
AsyncResult result = (AsyncResult)res;
try
{
((dynamic)result.AsyncDelegate).EndInvoke(res);
AsyncService.NotifyFinished();
}
catch (Exception e)
{
AsyncService.NotifyFailed(e);
}
}
public void AsyncRunning ()
{
ServiceCallback = OperationContext.Current.GetCallBackChannel<IServiceCallback>();
Action action = AsyncRunningProxy;
action.BeginInvoke(EndMethod, null);
}
}
Client Class:
public class ServiceRunner : IServiceCallback
{
private ManualResetEvent reset {get; set;}
public ServiceRunner()
{
reset = new ManualResetEvent(false);
}
public void Run()
{
AsyncServiceClient client = null;
InstanceContext instanceContext = new InstanceContext(this);
client = new AsyncServiceClient(instanceContext);
client.AsyncRunning();
reset.WaitOne();
}
public void NotifyProgress(int x)
{
Console.WriteLine(x);
}
public void NotifyFinished()
{
}
public void NotifyFailed(Exception e)
{
Console.WriteLine(e.Message);
reset.Set();
}
}
Edit: new client Class:
Client Class:
public class ServiceRunner : IServiceCallback
{
private ManualResetEvent reset { get; set; }
private string IsRunning { get; set; }
public ServiceRunner()
{
reset = new ManualResetEvent(false);
IsRunning = true;
}
public void Run()
{
AsyncServiceClient client = null;
InstanceContext instanceContext = new InstanceContext(this);
client = new AsyncServiceClient(instanceContext);
client.AsyncRunning();
new Thread(()=>
{
while(IsRunning)
{
try
{
client.IsAlive();
Thrad.Sleep(60 * 1000);
}
catch (Exception e) // The server is not responding.
{
NotifyFailed(e);
return;
}
}
}).Start();
reset.WaitOne();
}
public void NotifyProgress(int x)
{
Console.WriteLine(x);
}
public void NotifyFinished()
{
IsRunning = false;
reset.Set();
}
public void NotifyFailed(Exception e)
{
IsRunning = false;
Console.WriteLine(e.Message);
reset.Set();
}
}
In order to have more control of your clients request to the service, you should be able to use the inbuilt Task and Async support to monitor and if necessary handle connection delays.
The support for generating Task-based operations on the client side will be available to users who rely on proxies generated by our client generation tools (svcutil.exe or Add Service Reference), as well as to users who prefer to directly use ChannelFactory
The following code provides a rough example:
Task<string> task = new MyServiceClient().MyMethod();
if (task == await Task.WhenAny(task, Task.Delay(1000)))
{
Console.WriteLine(await task);
}
else
{
// handle delay …
}
Refer to the following MSDN blog entry for more information:
http://blogs.msdn.com/b/endpoint/archive/2010/11/13/simplified-asynchronous-programming-model-in-wcf-with-async-await.aspx
Regards,
As #adkSerenity mention you may implement timeout logic, but I guess your question not about that.
The callback method will be(and should be) called in case of exception for example connection lose or internal connection time out.
private static void CallbackSample(IAsyncResult asynchronousResult)
{
try
{
// State of request is asynchronous.
RequestState myRequestState=(RequestState) asynchronousResult.AsyncState;
HttpWebRequest myHttpWebRequest2=myRequestState.request;
myRequestState.response = (HttpWebResponse);
//next line may throw exception
myHttpWebRequest2.EndGetResponse(asynchronousResult);
}
catch(WebException e)
{
}
}
So async communication looks like fire and forget. Your callback method will be called when you get the result(exception too), but if you decide not handle it(custom time out logic) you must "foret" about callback processing. No way to check is alive(except of course custom api).

Categories