1

I have a Parallel.ForEach loop doing the following:

ConcurrentBag<Participant> participantsList = new ConcurrentBag<Participant>() {
    new Participant() { EMail = "Test.Mail@outlook.de", FirstName = "First1", LastName = "Last1"},
    new Participant() { EMail = "Test.Mail@gmail.de", FirstName = "First2", LastName = "Last2"},
    new Participant() { EMail = "Test.Mail@yahoo.de", FirstName = "First3", LastName = "Last3"},
};

Parallel.ForEach(participantsList, (p) =>
{
    var mail = new Email("REGISTERMAIL", p.FirstName, p.LastName);
    mail.AddRecipient(p.EMail);
    mail.Send();
});

What happens is that the ForEach-Loop only processes one participant successfully. The others two throw "Object reference not set to an instance of an object.".

I've first concluded this would be due to the process not being thread safe so I replaced participantsList (first being of type List<>) with a ConcurrentBag. But the error still occurs.

I can't see any other place where a thread would share one collection in my code since every thread gets its own instance if Email.

What could be the source of that error? Maybe static properties inside Email? Since they are not copied over for every instance...

Everything works fine with a normal foreach loop.

EDIT: There was one static dictionary inside the EMail class. I replaced it with a ConcurrentDictionary and that did not fix the problem yet.

Solution: Thanks to Luke Merrett I found the problem and was able to solve it:
I was referencing HttpContext.Current on multiple threads. Problem is that HttpContext.Current becomes null when you switch the thread. So I had to pass the the current HttpContext.Current into every thread I start:

HttpContext ctx = HttpContext.Current;
Parallel.ForEach(participantsList, (p) =>
{
    HttpContext.Current = ctx;
    var mail = new Email("REGISTERMAIL", p.FirstName, p.LastName);
    mail.AddRecipient(p.EMail);
    mail.Send();
});

More information

The Email class is a wrapper around System.Net.Mail. The contructor takes in a creation option which runs a standard email initialisation predefined by the user and stored in a static dictionary. It also takes in an array of objects to apply String.Format on it:

public Email(string creationFlag, params object[] formatObjects)
{
    Mail = new MailMessage();
    Smtp = new SmtpClient();

    Action<Email, object[]> action;
    if (OptionsToActionsMap.TryGetValue(creationFlag.ToString(), out action))
    {
        action(this, formatObjects);
    }
}

The following action is then run:

private static Action<Email, object[]> registerMail = new Action<Email, object[]>((mail, formatParams) =>
{
    mail.Smtp.Host = "smtp.sendgrid.net";

    mail.SetCredentials(WebConfigurationManager.AppSettings["mailAccount"],
                        WebConfigurationManager.AppSettings["mailPassword"]);

    mail.From = "no-reply@sender.de";

    mail.Subject = "Deine Anmeldung für die TNTC.";

    mail.AddAttachment(HttpContext.Current.Server.MapPath("~/img/TNTC-Logo.png"), "logo", new ContentType("image/png"));
    mail.AddAttachment(HttpContext.Current.Server.MapPath("~/img/Anfahrt.png"), "anfahrt", new ContentType("image/png"));

    mail.AddHtmlView(HttpContext.Current.Server.MapPath("~/EMail/MailBody.html"), formatParams);
});

Now here there are two methods called namely AddAttachment:

public void AddAttachment(string path, string contentId, ContentType ct)
{
    if (LinkedResources == null)
    {
        LinkedResources = new List<LinkedResource>();
    }

    var linkedResource = new LinkedResource(path, ct);
    linkedResource.ContentId = contentId;

    LinkedResources.Add(linkedResource);
}

and AddHtmlView which invokes AddView with a content type of "text/html":

public virtual void AddView(string path, string ctype, params object[] formatObjects)
{
    if (Views == null)
    {
        Views = new List<AlternateView>();
    }

    if (new ContentType(ctype) != null)
    {
        var view = AlternateView.CreateAlternateViewFromString(String.Format(File.ReadAllText(path), formatObjects), null, ctype);
        Mail.AlternateViews.Add(view);
    }
}

Now mail.Send() just adds the linked resources to every view and sends the mail:

public virtual void Send()
{
    if (EmailValid())
    {
        if (LinkedResources.Count() > 0)
        {
            foreach (var view in Mail.AlternateViews)
            {
                foreach (var linkedResource in LinkedResources)
                {
                    view.LinkedResources.Add(linkedResource);
                }
            }
        }

        foreach (var view in Views)
        {
            Mail.AlternateViews.Add(view);
        }

        Smtp.Send(Mail);
    }
}
FunkyPeanut
  • 1,152
  • 2
  • 9
  • 28
  • What is the `Email` class in this context, is it a wrapper around `SmtpClient` / `MailMessage`? Edit: to expand on my comment; I'm running this code locally with mocked `Participant` and `Email` classes and no exception occurs. Would we be able to see the insides of those classes? – Luke Merrett Jul 06 '15 at 20:03
  • Where exactly is the error occuring? I copied this code with dummy Participant and Email classes and got no error – oppassum Jul 06 '15 at 20:07
  • 1
    Sure, I'll provide you with more information just give me the time to edit it in :) – FunkyPeanut Jul 06 '15 at 20:16
  • Please do more debugging. If after following the advice on the duplicate question, you are still unable to figure this out, provide [a good, _minimal_, _complete_ code example](https://stackoverflow.com/help/mcve) that reliably reproduces the problem, along with a detailed description of what debugging you've done, and a problem description that is more specific than simply stating that `NullReferenceException` occurs. – Peter Duniho Jul 06 '15 at 20:51
  • You are totally right about me haviing to give more specific information. But after spending ~ one hour debugging I could not really get more information out of this. The debugger for example simply throws an exception after jumping out of the mail.Send() method. I was unable to find out where exaclty the error is thrown. Do you have any tips on how to debug asynchronous operations? Maybe you need some more specific knowledge there.. – FunkyPeanut Jul 06 '15 at 20:54

1 Answers1

1

I noticed you are using HttpContext in the action, like this:

mail.AddAttachment(HttpContext.Current.Server.MapPath(
    "~/img/TNTC-Logo.png"), "logo", new ContentType("image/png"));

mail.AddAttachment(HttpContext.Current.Server.MapPath(
    "~/img/Anfahrt.png"), "anfahrt", new ContentType("image/png"));

mail.AddHtmlView(HttpContext.Current.Server.MapPath(
    "~/EMail/MailBody.html"), formatParams);

As you are running the code asynchronously outside the main response thread, HttpContext.Current may be disposed before your processing completes (i.e: the server has returned a response, so there's no longer any context.)

To get around this you'd have to pass the mapped paths in before you kick off the parallel threads, or store them so they do not depend on the HttpContext.Current existing at the time they are called.

A question along these lines was posted here.

Community
  • 1
  • 1
Luke Merrett
  • 5,724
  • 8
  • 38
  • 70
  • Hey! Thanks alot for your promising answer. As a quick fix i just commented out the lines using the HttpContext.Current but unfortnuately the error still occurs. – FunkyPeanut Jul 06 '15 at 20:59
  • Cheers for the update bud; if you could throw in a stack trace showing where the exception is occurring that would help. – Luke Merrett Jul 06 '15 at 21:01
  • I would but the stack trace does not give any useful information since it stores only like 100 at a time - its full of async overhead and not a single call (except the most recent) belongs to my own code. Is there a ways to display the complete stack trace? – FunkyPeanut Jul 06 '15 at 21:07
  • You said it shows the most recent call, does it provide a line number? Or is it too far away from the actual exception? I don't know how to get a full stack trace (love to find out though!) – Luke Merrett Jul 06 '15 at 21:12
  • It just showed the line of the actuall code that called the method in the first place. But I've changed some debug settings and now the compiler is givin gme some more useful information. I'll try to make advantage of that and will report back on my results :) – FunkyPeanut Jul 06 '15 at 21:13
  • 1
    Having changed the debugger options it now actually shows where the NullReferenceException is thrown! And guess what: You were right with your assumption that HttpContext.Current is null. I'll do as you suggested and report back :) – FunkyPeanut Jul 06 '15 at 21:21
  • Out of interest, which debugger option did you change to get a better stack trace? Glad to here my assumption panned out! – Luke Merrett Jul 06 '15 at 21:23
  • 1
    I checked the option "Enable just my code" under Debug -> Options and Settings -> Debugging -> General. Initially I just wanted the external entries in the Stack Trace to dissapear. But after enabling this I somehow got much more detailed Debugger results. – FunkyPeanut Jul 06 '15 at 21:26
  • You were right in your answer. I'll put the solution up above. Thanks! – FunkyPeanut Jul 06 '15 at 21:33