Singleton saga in MassTransit - c#

What I'm trying to do is to create only one saga instance, not updated by subsequent events. So for example if I send EventA at 09:00, with CorrelationId equal to 979158a2-dfa0-45f0-9107-8b8b8028bb9f and then the saga for that event is still alive at 10:00, then if at 10:00 I send another instance of EventA with the same CorrelationId that was sent previously, I don't want it to overwrite the previous saga state but instead I would like this event to be ignored by MT. I kind of achieved that, because my saga state is not overriden (only its version is incremented), but the problem is that MT throws an error when the new event arrives. The exception is (+one log entry above it):
[18:41:12 DBG] SAGA:App.Services.Communicators.Amazon.Dispatcher.Api.Sagas.GetOrdersState:0368a723-a819-450f-b6e5-9211d1d6a3a9 Dupe App.Services.Financial.Contracts.GetOrdersCommand
MongoDB.Driver.MongoWriteException: A write operation resulted in an error.
E11000 duplicate key error collection: Dispatcher.get.orders.states index: _id_ dup key: { _id: BinData(3, 23A7680319A80F45B6E59211D1D6A3A9) }
---> MongoDB.Driver.MongoBulkWriteException`1[App.Services.Communicators.Amazon.Dispatcher.Api.Sagas.GetOrdersState]: A bulk write operation resulted in one or more errors.
E11000 duplicate key error collection: Dispatcher.get.orders.states index: _id_ dup key: { _id: BinData(3, 23A7680319A80F45B6E59211D1D6A3A9) }
at MongoDB.Driver.MongoCollectionImpl`1.BulkWriteAsync(IClientSessionHandle session, IEnumerable`1 requests, BulkWriteOptions options, CancellationToken cancellationToken)
at MongoDB.Driver.MongoCollectionImpl`1.UsingImplicitSessionAsync[TResult](Func`2 funcAsync, CancellationToken cancellationToken)
at MongoDB.Driver.MongoCollectionBase`1.InsertOneAsync(TDocument document, InsertOneOptions options, Func`3 bulkWriteAsync)
--- End of inner exception stack trace ---
at MongoDB.Driver.MongoCollectionBase`1.InsertOneAsync(TDocument document, InsertOneOptions options, Func`3 bulkWriteAsync)
at MassTransit.MongoDbIntegration.Saga.Context.MongoDbSagaRepositoryContext`2.Insert(TSaga instance)
And the important part of the saga config:
Event(() => GetOrdersIntegrationEventReceived, e =>
e
.CorrelateById(x => x.Message.CorrelationId)
.SelectId(x => x.Message.CorrelationId)
.SetSagaFactory(ctx => new GetOrdersState
{
CorrelationId = ctx.Message.CorrelationId,
PartnerId = ctx.Message.PartnerId,
LastUpdatedBeforeFromRequest = ctx.Message.LastUpdatedBefore
})
.InsertOnInitial = true);
Is what I'm trying to do even possible?

MassTransit will only create one instance of the saga for each CorrelationId, so your reasoning is correct. However, you're approach is a little off and could use some tweaking.
For instance, your event configuration:
Event(() => GetOrdersIntegrationEventReceived, e => e.CorrelateById(x => x.Message.CorrelationId));
That's it, all the extra stuff is why you're seeing messages in the debug log.
Then, after the event is declared:
During(Initial, Created,
When(GetOrdersIntegrationEventReceived)
.Then(context =>
{
context.Instance.PartnerId = context.Message.PartnerId,
context.Instance.LastUpdatedBeforeFromRequest = context.Message.LastUpdatedBefore
})
.TransitionTo(Created)
);
That's it.

Related

Invalid NEST response built from a unsuccessful () low level call on POST: when trying to run a console app in C#

I am new to elastic search. I am trying to use the code to search text. getting the Error "{Invalid NEST response built from a unsuccessful () low level call on POST: /foo/_doc}" when trying to run the code. I think I am missing something how to use Nest nuGet any help is welcome
using IdentityServer4.Models;
using Nest;
using System;
using System.Collections.Generic;
using System.Linq;
using static System.Net.Mime.MediaTypeNames;
class Program
{
static void Main(string[] args)
{
var settings = new ConnectionSettings(new Uri("http://127.0.0.1:9200"))
.DefaultIndex("foo");
var client = new ElasticClient(settings);
var indexName = "indexName";
if (client.IndexExists(indexName).Exists)
{
client.DeleteIndex(indexName);
}
var createIndexResponse = client.CreateIndex(indexName, c => c
.Settings(s => s
.Analysis(a => a
.Analyzers(an => an
.Custom("my_analyzer", ca => ca
.Tokenizer("standard")
.CharFilters("html_strip")
.Filters("lowercase")
)
)
)
)
.Mappings(m => m
.Map<Document>(mp => mp
.AutoMap()
.Properties(p => p
.Text(t => t
.Name(n => n.Title)
.Analyzer("my_analyzer")
)
.Text(t => t
.Name(n => n.Content)
.Analyzer("my_analyzer")
)
)
)
)
);
var doc = new Document
{
Title = "Friends are friendlier friendlies that are friendly and classify the friendly classification class.",
// Remove stop word like are, and, that,the
Content = "Flowery flowers flow through following the flower flows."
};
var indexResponse = client.IndexDocument(doc);
// Error occurred here
Console.WriteLine(indexResponse.Result);
Console.ReadKey();
}
}
Document class
class Document
{
public string Title { get; set; }
public string Content { get; set; }
}
Debugging Information
"Invalid NEST response built from a unsuccessful () low level call on PUT: /indexName\r\n# Audit trail of this API call:\r\n - [1] ProductCheckOnStartup: Took: 00:00:02.0264861\r\n - [2] ProductCheckFailure: Node: http://127.0.0.1:9200/ Took: 00:00:02.0261849\r\n# OriginalException: Elasticsearch.Net.ElasticsearchClientException: The client is unable to verify that the server is Elasticsearch due to an unsuccessful product check call. Some functionality may not be compatible if the server is running an unsupported product. Call: Status code unknown from: GET /\r\n ---> Elasticsearch.Net.PipelineException: The client is unable to verify that the server is Elasticsearch due to an unsuccessful product check call. Some functionality may not be compatible if the server is running an unsupported product.\r\n ---> System.Net.Http.HttpRequestException: No connection could be made because the target machine actively refused it.\r\n ---> System.Net.Sockets.SocketException (10061): No connection could be made because the target machine actively refused it.\r\n at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)\r\n --- End of inner exception stack trace ---\r\n at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)\r\n at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean allowHttp2, CancellationToken cancellationToken)\r\n at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)\r\n at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(Task1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)\r\n at Elasticsearch.Net.HttpConnection.Request[TResponse](RequestData requestData)\r\n --- End of inner exception stack trace ---\r\n at Elasticsearch.Net.RequestPipeline.ThrowIfTransientProductCheckFailure()\r\n at Elasticsearch.Net.RequestPipeline.Ping(Node node)\r\n at Elasticsearch.Net.Transport1.Ping(IRequestPipeline pipeline, Node node)\r\n at Elasticsearch.Net.Transport`1.Request[TResponse](HttpMethod method, String path, PostData data, IRequestParameters requestParameters)\r\n --- End of inner exception stack trace ---\r\n# Request:\r\n<Request stream not captured or already read to completion by serializer. Set DisableDirectStreaming() on ConnectionSettings to force it to be set on the response.>\r\n# Response:\r\n<Response stream not captured or already read to completion by serializer. Set DisableDirectStreaming() on ConnectionSettings to force it to be set on the response.>\r\n"

Azure Service Bus: AMQP object is closing. Operation 'attach' cannot be performed

I'm using Azure Service Bus Topics with the AMQP protocol in the West Europe datacenter.
This is a schematic way of how the solution implented works:
private Microsoft.Azure.ServiceBus.SubscriptionClient CreateClient() {
string serviceBusConnectionString;
strin serviceBusTopicName;
string subscriptionName;
var subscriptionClient = new Microsoft.Azure.ServiceBus.SubscriptionClient(serviceBusConnectionString, serviceBusTopicName, subscriptionName) {
PrefetchCount = 0
};
return subscriptionClient;
}
public async Task<ISubscriptionClient> CreateSubscriptionClientAsync() {
//Some logic on the subscriptionClient, caching, creating a new one if it doesn't exists, etc.
return CreateClient()
}
private async Task CallbackAsync(Message msg, CancellationToken cancellationToken) {
//Do stuff with the Message
//when you're done Completethe message
}
public async Task<string> OpenAsync(CancellationToken cancellationToken) {
subscriptionClient = await CreateSubscriptionClientAsync().ConfigureAwait(false);
var messageHandlerOptions = new MessageHandlerOptions(ExceptionReceivedHandlerAsync) {
AutoComplete = false,
MaxAutoRenewDuration = TimeSpan.FromHours(8)
};
subscriptionClient.RegisterMessageHandler(CallbackAsync, messageHandlerOptions);
return string.Empty;
}
But last night I had couple of thousand exception like this one:
Exception: "System.InvalidOperationException: The AMQP object g2b-sessionXXXXXXXX is closing. Operation 'attach' cannot be performed.
at Microsoft.Azure.ServiceBus.Core.MessageReceiver.<OnReceiveAsync>d__86.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.Azure.ServiceBus.Core.MessageReceiver.<>c__DisplayClass64_0.<<ReceiveAsync>b__0>d.MoveNext()
I'm using the Microsoft.Azure.ServiceBus v3.1.0.0,
On line I didn't find anything useful about this "Operation 'attach' cannot be performed".
On the message bus exception list page I didn't find any information about this specific problem. And in the status history site there is no reference about any outage involving Service Bus.
Did anyone experience this problem before?
What is causing the exception?
Do I need to implement any retry logic? How?
Any help is apreciated, thanks.

Memory leak for nhibernate and fluent nhibernate

I have windows service that is basically reading messages from customer and we do process and sends them over HTTP/TCP/File etc. Service is written in C#, for database interaction I use NHibernate and TPL task.
For every batch of message service reads and inserts into SQL server database in separate TPL Task and same messages is then fetched from database in another TPL task we send them over HTTP/TCP/File after processing, and we also save this records into database using NHibernate ISession.
piece of code is below
public Func<ISession> GetSession { get; set; }
[Transaction]
public string SaveInMessage(ISession session, string message)
{
try
{
using (var session = GetSession()){
session.Transaction.Begin();
var inMessage = new InMessage();
var task = new Task(()=> InsertToDatabase(session, inMessage));
session.Transaction.Commit();
}
}
catch(Exception ex)
{
session.Transaction.Rollback();
}
}
public void InsertToDatabase(ISession session, InMessage inMessage){
session.SaveOrUpdate(inMessage);
}
[Transaction]
public bool SaveOutMessage()
{
try
{
using (var session = GetSession()){
session.Transaction.Begin();
var inMessage = session.Load<InMessage>();
var outMessage = new OutMessage();
var task = new Task(()=> ConvertMessage(outMessage, inMessage, session));
var task = new Task(()=> SendMessage(outMessage, session, outProtocol));
session.Transaction.Commit();
}
}
catch(Exception ex)
{
session.Transaction.Rollback();
}
}
public void ConvertMessage(OutMessage outMessage, ISession session, Http url)
{
conversion logic goes here;
}
public void SendMessage(OutMessage outMessage,ISession session,Protocol outProtocol)
{
Sending message logic goes here;
session.SaveOrUpdate(inMessage);
}
So, in above I have used Castle.Windsor for IoC and Transaction attribute used in these two methods are from same.
I am keep getting below AggregateException along with OutOfMemoryException within NHibernate and TPL.
System.AggregateException: One or more errors occurred. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
at System.String.ConcatArray(String[] values, Int32 totalLength)
at System.String.Concat(Object[] args)
at NHibernate.Engine.Cascade.CascadeOn(IEntityPersister persister, Object parent, Object anything)
at NHibernate.Event.Default.AbstractFlushingEventListener.CascadeOnFlush(IEventSource session, IEntityPersister persister, Object key, Object anything)
at NHibernate.Event.Default.AbstractFlushingEventListener.PrepareEntityFlushes(IEventSource session)
at NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(FlushEvent event)
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
at NHibernate.Impl.SessionImpl.Flush()
at NHibernate.Transaction.AdoTransaction.Commit()
at Service.ProcessMessage(Message message, ISession session) in C:\Project\Service\ProcessMessage.cs:line 247
at Service.ProcessMessage.<>c__DisplayClass22_0.<SendMessages>b__0(Task c) in C:\Project\Service\ProcessMessage.cs:line 74
at System.Threading.Tasks.ContinuationTaskFromTask.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.Tasks.Task.Wait(CancellationToken cancellationToken)
at Service.ProcessMessage.SendMessages(CancellationToken cancelToken) in C:\Project\Service\ProcessMessage.cs:line 73
---> (Inner Exception #0) System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
at System.String.ConcatArray(String[] values, Int32 totalLength)
at System.String.Concat(Object[] args)
at NHibernate.Engine.Cascade.CascadeOn(IEntityPersister persister, Object parent, Object anything)
at NHibernate.Event.Default.AbstractFlushingEventListener.CascadeOnFlush(IEventSource session, IEntityPersister persister, Object key, Object anything)
at NHibernate.Event.Default.AbstractFlushingEventListener.PrepareEntityFlushes(IEventSource session)
at NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(FlushEvent event)
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
at NHibernate.Impl.SessionImpl.Flush()
at NHibernate.Transaction.AdoTransaction.Commit()
at Service.ProcessMessage.Process(Message message, ISession session) in C:\Project\Service\ProcessMessage.cs:line 247
at Service.ProcessMessage.<>c__DisplayClass22_0.<SendMessages>b__0(Task c) in C:\Project\Service\ProcessMessage.cs:line 74
at System.Threading.Tasks.ContinuationTaskFromTask.InnerInvoke()
at System.Threading.Tasks.Task.Execute()<---
LogExtension.Event => LogExtension.Event => LogExtension.LogEvent
Above error is coming after 4-5 hours after windows service is started.
Any hint or help is appreciated.
.Net framework version I use is 4.5
NHibernate version is 4.0.4.4000
Castle.Windsor version is 3.3.0
FluentNhibernate version is 1.3.0.733
Castle.Facilities.NHibernate version is 0.7.1.23602
Castle.Facilities.AutoTx version is 3.2.207.2207
Castle.Core version is 3.3.3
You haven't posted your logic for GetSession(), but I get the impression you are re-using the same session for all of the messages. Sessions are supposed to be short lived. Keeping a single session open will result in the first level cache growing to the point where you get performance degradation and eventually run out of memory.

Faking Confluent .NET Kafka consumers

Environment: .NET 4.5.
FakeItEasy : 4.0.0
I am trying to create a fake object of Kafka consumer, below is the syntax I use:
var fakeconsumer = A.Fake<Consumer<Null, string>>((x => x.WithArgumentsForConstructor(() => new Consumer<Null, string>(A.Dummy<IEnumerable<KeyValuePair<string, object>>>(), A.Dummy<IDeserializer<Null>>(), A.Dummy<StringDeserializer>()))));
The code for Kafka client is here: https://github.com/confluentinc/confluent-kafka-dotnet/blob/master/src/Confluent.Kafka/Consumer.cs
As you can see, I am invoking the Fake call with correct parameters required for the constructor. However I keep getting the follow error message : "No constructor matches the passed arguments for constructor.".
Any help is greatly appreciated.
Thank you
Edit:
at FakeItEasy.Creation.CastleDynamicProxy.CastleDynamicProxyGenerator.CreateProxyGeneratorResult(Type typeOfProxy, ProxyGenerationOptions options, IEnumerable1 additionalInterfacesToImplement, IEnumerable1 argumentsForConstructor, IFakeCallProcessorProvider fakeCallProcessorProvider) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\CastleDynamicProxy\CastleDynamicProxyGenerator.cs:line 125
at FakeItEasy.Creation.CastleDynamicProxy.CastleDynamicProxyGenerator.GenerateProxy(Type typeOfProxy, ProxyGenerationOptions options, IEnumerable1 additionalInterfacesToImplement, IEnumerable1 argumentsForConstructor, IFakeCallProcessorProvider fakeCallProcessorProvider) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\CastleDynamicProxy\CastleDynamicProxyGenerator.cs:line 86
at FakeItEasy.Creation.FakeObjectCreator.GenerateProxy(Type typeOfFake, IProxyOptions proxyOptions, IEnumerable1 argumentsForConstructor) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\FakeObjectCreator.cs:line 113
at FakeItEasy.Creation.FakeObjectCreator.CreateFake(Type typeOfFake, IProxyOptions proxyOptions, DummyCreationSession session, IDummyValueResolver resolver, Boolean throwOnFailure) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\FakeObjectCreator.cs:line 36
at FakeItEasy.Creation.DefaultFakeAndDummyManager.CreateFake(Type typeOfFake, Action1 optionsBuilder) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\DefaultFakeAndDummyManager.cs:line 41
at FakeItEasy.A.Fake[T](Action`1 optionsBuilder) in C:\projects\fakeiteasy\src\FakeItEasy\A.cs:line 47
I believe I've reproduced your problem. Here's the full exception that I see:
FakeItEasy.Core.FakeCreationException :
Failed to create fake of type Confluent.Kafka.Consumer`2[Confluent.Kafka.Null,System.String] with the specified arguments for the constructor:
No constructor matches the passed arguments for constructor.
An exception of type System.ArgumentException was caught during this call. Its message was:
'group.id' configuration parameter is required and was not specified.
at Confluent.Kafka.Consumer..ctor(IEnumerable`1 config)
at Confluent.Kafka.Consumer`2..ctor(IEnumerable`1 config, IDeserializer`1 keyDeserializer, IDeserializer`1 valueDeserializer)
at Castle.Proxies.Consumer`2Proxy..ctor(IInterceptor[] , IEnumerable`1 , IDeserializer`1 , IDeserializer`1 )
at FakeItEasy.Core.DefaultExceptionThrower.ThrowFailedToGenerateProxyWithArgumentsForConstructor(Type typeOfFake, String reasonForFailure)
at FakeItEasy.Creation.FakeObjectCreator.AssertThatProxyWasGeneratedWhenArgumentsForConstructorAreSpecified(Type typeOfFake, ProxyGeneratorResult result, IProxyOptions proxyOptions)
at FakeItEasy.Creation.FakeObjectCreator.CreateFake(Type typeOfFake, IProxyOptions proxyOptions, DummyCreationSession session, IDummyValueResolver resolver, Boolean throwOnFailure)
at FakeItEasy.Creation.DefaultFakeAndDummyManager.CreateFake(Type typeOfFake, Action`1 optionsBuilder)
at FakeItEasy.A.Fake[T](Action`1 optionsBuilder)
Kafka.cs(14,0): at FakeItEasyQuestions2015.Kafka.MakeConsumer()
You can see that FakeItEasy itself encountered an exception while calling the Consumer class's constructor:
An exception of type System.ArgumentException was caught during this call. Its message was:
'group.id' configuration parameter is required and was not specified.
This was thrown from the Consumer constructor on line 756:
if (config.FirstOrDefault(prop => string.Equals(prop.Key, "group.id", StringComparison.Ordinal)).Value == null)
{
throw new ArgumentException("'group.id' configuration parameter is required and was not specified.");
}
It seems that
Consumer(IEnumerable<KeyValuePair<string, object>> config,
IDeserializer<TKey> keyDeserializer,
IDeserializer<TValue> valueDeserializer)`
Has some requirements on its inputs that aren't being met. In particular, it seems it needs config to contain one element with the key "group.id". If I change your code to
var fakeconsumer = A.Fake<Consumer<Null, string>>(
(x => x.WithArgumentsForConstructor(
() => new Consumer<Null, string>(new [] { new KeyValuePair<string, object>("group.id", "hippo")},
A.Dummy<IDeserializer<Null>>(),
A.Dummy<StringDeserializer>()))));
The fake is created.
I notice that you cross-posted to FakeItEasy Issue 1176. I'll make a note there to come here for this answer.
Not 100% related to the original question but in my library (Silverback: https://github.com/BEagle1984/silverback) I have a mocked in-memory implementation of the Confluent.Kafka library, that allows for kinda sophisticated integration tests. See some simple examples: https://silverback-messaging.net/concepts/broker/testing.html.
Just to give you an idea:
[Fact]
public async Task SampleTest()
{
// Arrange
var testingHelper = _factory.Server.Host.Services
.GetRequiredService<IKafkaTestingHelper>();
var producer = testingHelper.Broker
.GetProducer(new KafkaProducerEndpoint("tst-topic"));
// Act
await producer.ProduceAsync(new TestMessage { Content = "abc" });
await testingHelper.WaitUntilAllMessagesAreConsumedAsync();
// Assert
testingHelper.Spy.OutboundEnvelopes.Should().HaveCount(1);
testingHelper.Spy.InboundEnvelopes.Should().HaveCount(1);
testingHelper.Spy.InboundEnvelopes[0].Message.As<TestMessage>
.Content.Should().Be("abc");
}
The implementation is not that complex but it supports partitions and a simulation of the rebalance mechanism.
See the implementation: https://github.com/BEagle1984/silverback/tree/master/src/Silverback.Integration.Kafka.Testing/Messaging/Broker/Kafka
I am no expert on the Consumer class for Kafka, but it looks like your are invoking it like:
Consumer<Null, string>
But the only constructors I can find in the code are:
public Consumer(
IEnumerable<KeyValuePair<string, object>> config)
public Consumer(
IEnumerable<KeyValuePair<string, object>> config,
IDeserializer<TKey> keyDeserializer,
IDeserializer<TValue> valueDeserializer)
So there is no match. It looks like you want to use the first one, so you are missing the IEnumerable part.

NullReference when using Task.WaitAll

I have a Windows service that spawns objects that do work. The objects have two methods that get kicked off as Tasks. During housekeeping or shutdown, I call a Stop method on the object that tell the Tasks to stop. I have a list of the Tasks created for the objects' methods, so I do a Task.WaitAll for them. But, I'm getting a NullReferenceException during shutdown. I thought one of the Task objects was null, so I tested for it with a .Where(pt => pt != null).ToArray(), but that didn't work.
Here's a snippet:
var peProcessor = new PrintExpertProcessor(runId);
processorTasks.Add(Task.Factory.StartNew(() => peProcessor.ProcessRun()));
processorTasks.Add(Task.Factory.StartNew(() => peProcessor.StartMonitor()));
processors.Add(peProcessor);
// Later in the code
Task.WaitAll(processorTasks.ToArray()); // System.AggregateException: System.NullReferenceException:
I think that the Task is not null, but that the original processor object is null. But, I'm not sure how to check that or prevent this. I'm new to Tasks, so I'm still getting my head around it.
Thoughts?
Full Error Message:
System.AggregateException: System.NullReferenceException: Object reference not set to an instance of an object.
at WOW.PrintExpert.AwdProcessor.Code.PrintExpertProcessor.StopProcessor()
at WOW.PrintExpert.AwdProcessor.Code.PrintExpertProcessor.ProcessRun()
at System.Threading.Tasks.Task.Execute()
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken)
at WOW.PrintExpert.AwdProcessorService.ProcessorService.OnStop()
---> (Inner Exception #0) System.NullReferenceException: Object reference not set to an instance of an object.
at WOW.PrintExpert.AwdProcessor.Code.PrintExpertProcessor.StopProcessor()
at WOW.PrintExpert.AwdProcessor.Code.PrintExpertProcessor.ProcessRun()
at System.Threading.Tasks.Task.Execute()<---
As explained at the documentation linked by DJ KRAZE, the AggregateException means that the NullReferenceException was thrown by one of the tasks (not that the processorTasks collection is null, nor one of its items, as that would produce an ArgumentNullException)
Check the callstack of the NullReferenceException (which would be found at the AggregateException.InnerExceptions property), or simply set your debugger to break on thrown NullReferenceExceptions, in order to find the actual exception.
Ok, here's what I went with. It seems to be working as I want and it is testing well.
// Service class
private Dictionary<int, Task> processorMonitorTasks = new Dictionary<int, Task>();
private Dictionary<int, Task> processorTasks = new Dictionary<int, Task>();
When new work is received:
// Spawn new processor object
var peProcessor = new PrintExpertProcessor(runId);
// Create tasks for the processors methods
processorTasks.Add(runId, Task.Run(() => peProcessor.ProcessRun()));
processorMonitorTasks.Add(runId, Task.Run(() => peProcessor.StartMonitor()));
// Add the processor the collection
processors.Add(peProcessor);
When the service shuts down, it does this:
// Copying the object references to a new list prevents enumeration changed exceptions.
foreach (var proc in processors.ToList())
{
if (proc != null && !proc.IsStopped)
{
// Direct that each object come to a stop
proc.StopProcessor();
}
}
// Now, wait for each one to stop.
Task.WaitAll(processorTasks.Values.ToArray());
Task.WaitAll(processorMonitorTasks.Values.ToArray());

Categories

Resources