Parallel For Each 抛出 "No reference to object" 异常

Parallel For Each throwing "No reference to object" exception

我有一个 Parallel.ForEach 循环执行以下操作:

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();
});

发生的情况是 ForEach-Loop 只成功处理了一个参与者。其他两个扔 "Object reference not set to an instance of an object.".

我首先得出结论,这是由于进程不是线程安全的,所以我用 ConcurrentBag 替换了 participantsList(首先是 List<> 类型)。但是还是报错。

我看不到任何其他线程可以在我的代码中共享一个集合的地方,因为如果电子邮件,每个线程都有自己的实例。

该错误的来源可能是什么?也许电子邮件中的静态属性?由于它们不会在每个实例中都被复制...

正常的 foreach 循环一切正常。

编辑:电子邮件中有一个静态字典class。我将其替换为 ConcurrentDictionary,但仍未解决问题。

解决方法: 感谢 Luke Merrett,我找到了问题并解决了它:
我在多个线程上引用 HttpContext.Current。问题是当您切换线程时 HttpContext.Current 变为 null。所以我必须将当前 HttpContext.Current 传递到我启动的每个线程中:

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();
});

更多信息

电子邮件 class 是 System.Net.Mail 的包装。 构造函数接受一个创建选项,该选项 运行 是用户预定义并存储在静态字典中的标准电子邮件初始化。它还接收一个对象数组以在其上应用 String.Format:

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);
    }
}

接下来的动作是运行:

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);
});

现在这里有两个方法叫做 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);
}

和 AddHtmlView 调用内容类型为 "text/html":

的 AddView
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);
    }
}

现在 mail.Send() 只是将链接的资源添加到每个视图并发送邮件:

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);
    }
}

我注意到您在操作中使用了 HttpContext,如下所示:

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);

由于您是 运行 主响应线程之外的异步代码,HttpContext.Current 可能会在您的处理完成之前处理掉(即:服务器已返回响应,因此不再有任何上下文.)

要解决这个问题,您必须在启动并行线程之前传递映射路径,或者存储它们,以便它们不依赖于调用它们时存在的 HttpContext.Current

A question along these lines was posted here.