Exception Handling not working in Parallel.ForEach
Problem situation
In my project I call into P开发者_开发问答owerShell to retrieve a list of organizations that are configured in Exchange 2010. Then I have to do something for each organization. Invoking PowerShell is sloooow, so if I'd to all the operations in sequence, it would take minutes. You can, however, create a PowerShell Runspace Pool and execute commands parallel, which saves a lot of time. So this is what I did:
public void MainMethod()
{
var organizations = exchangeRepository.GetOrganizations();
Parallel.ForEach(
organizations,
organization =>
{
try
{
exchangeRepository.DoSomethingWithAnOrganization(organization);
}
catch (Exception ex)
{
...
}
}
);
}
Problem
This works like a charm with one exception. The DoSomethingWithAnOrganization method has a try-catch and can handle some exceptions. However, whenever an Exception occurs, it isn't handled by the catch in the DoSomething-method, but immediately jups to the catch statement of the MainMethod.
(Note: The catch-statement of the DoSomething-method is called fine if I simple use foreach(... in ...) in stead of Parallel.ForEach).
Question
Why can't I handle the exception in the DoSomething-method? Is there a way to DoSomething parallel differently so the catch of that method is called if an Exception occurs?
Thank you!
This is the DoSomething-method:
public IEnumerable<Mailbox> Invoke(string organizationName)
{
try
{
var command = new PSCommand()
.AddCommand("Get-Mailbox")
.AddParameter("Organization", organizationName);
var result = Invoke(command);
var mailboxes =
from mailbox in result.Results
select new Mailbox()
{
Organization = organizationName,
Name = (string)mailbox.Properties["Name"].Value,
Identity = mailbox.Properties["Identity"].Value.ToString(),
Plan =
mailbox.Properties["MailboxPlan"].Value == null
? null
: (string)mailbox.Properties["MailboxPlan"].Value
.GetType().GetProperty("Name")
.GetValue(mailbox.Properties["MailboxPlan"].Value, null),
};
return mailboxes;
}
catch (Exception ex)
{
Log.Error(ex.Message, ex);
throw new Exceptions.ReportingServiceException(Exceptions.ExceptionType.Technical, ex.Message, ex);
}
}
Without seeing your code, I'm guessing that if the code works in sequential (single thread) but not in parallel (multiple threads) then you are getting an exception that DoSomethingWithAnOrganisation was not expecting (at least as a sequential operation) but is getting now you are running it in parallel.
You are sharing exchangeRepository
between tasks (threads) and if that contains mutable state then it could be what is causing the issue. You can't mutate shared data/state between threads without synchronising access to that data/state with locks. Often you'll find that you can get much better performance by refactoring your code to work without shared state than you will by trying to synchronise the code - which is the bottle neck in multi-threaded code.
If you can answer the questions I gave above then I might be able to give you a more specific answer.
I refactored the MainMethod to:
var organizations = ExchangeRepository.GetOrganizations().AsParallel();
var someInfo = organizations.SelectMany(organization => ExchangeRepository.DoSomethingWithOrganization(organization.Name));
and now the catch in the DoSomething-method IS called. I don't know why it does work this way and why it didn't the other way.
精彩评论