问题
I'm a threading newbie and am trying to use SemaphoreSlim to allow me to run a set number of long tasks simultaneously.
My challenge is that, given the way I've written it, any exceptions are not being captured correctly.
Here's a very simplified example of my current code:
public void ThreadTest()
{
try
{
var currentTasks = new List<Task>();
SemaphoreSlim maxThread = new SemaphoreSlim(2);
for (int i = 1; i < 5; ++i)
{
maxThread.Wait();
var testTask = Faulty().ContinueWith(tsk => maxThread.Release());
currentTasks.Add(testTask);
}
Task.WaitAll(currentTasks.ToArray());
Debug.WriteLine("End - We shouldn't have gotten here");
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
private async Task Faulty()
{
throw new Exception("Never reach the awaiter");
await Task.Factory.StartNew(() => Thread.Sleep(3000));
}
And, unfortunately, with the ContinueWith in there, I get to the "End - We shouldn't have gotten here" message rather than the error message I'd have wanted to get to.
How could I update this code to run correctly? Again, I apologize if this is completely wrong, this is a newbie's attempts at putting stuff together from stuff I've found online - Any and all suggestions to do this correctly are really appreciated!!!
回答1:
How could I update this code to run correctly?
Pretty simple: don't use ContinueWith. Use await instead:
public void ThreadTest()
{
try
{
var currentTasks = new List<Task>();
SemaphoreSlim maxThread = new SemaphoreSlim(2);
for (int i = 1; i < 5; ++i)
{
maxThread.Wait();
var testTask = TestAsync(maxThread);
currentTasks.Add(testTask);
}
Task.WaitAll(currentTasks.ToArray());
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
private async Task TestAsync(SemaphoreSlim maxThread)
{
try
{
await FaultyAsync();
}
finally
{
maxThread.Release();
}
}
private async Task FaultyAsync()
{
throw new Exception("Never reach the awaiter");
await Task.Run(() => Thread.Sleep(3000));
}
I also made a couple of other changes: added an Async postfix to follow the async naming convention, and replaced StartNew with Run since StartNew is dangerous (as I describe on my blog).
The code still doesn't end up quite right. The question for you is: do you want asynchronous concurrency or parallel concurrency? And that all comes down to the Task.Run(() => Thread.Sleep(3000)) line in FaultyAsync.
If that's a placeholder for a truly asynchronous (e.g., I/O) operation, then the ThreadTest should be made asynchronous and use Task.WhenAll instead of WaitAll, as such:
public async Task TestAsync()
{
try
{
var currentTasks = new List<Task>();
SemaphoreSlim throttle = new SemaphoreSlim(2); // Not "maxThread" since we're not dealing with threads anymore
for (int i = 1; i < 5; ++i)
{
var testTask = TestAsync(throttle);
currentTasks.Add(testTask);
}
await Task.WhenAll(currentTasks);
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
private async Task TestAsync(SemaphoreSlim throttle)
{
await throttle.WaitAsync();
try
{
await FaultyAsync();
}
finally
{
maxThread.Release();
}
}
private async Task FaultyAsync()
{
throw new Exception("Never reach the awaiter");
await Task.Delay(3000); // Naturally asynchronous operation
}
On the other hand, if Task.Run(() => Thread.Sleep(3000)) is a placeholder for a truly synchronous (e.g., CPU) operation, then you should use higher-level parallel abstractions instead of creating your own tasks by hand:
public void ThreadTest()
{
try
{
var options = new ParallelOptions { MaxDegreeOfParallelism = 2 };
Parallel.For(1, 5, options, i => Faulty());
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
private void Faulty()
{
throw new Exception("Never reach the work");
Thread.Sleep(3000); // Naturally synchronous operation
}
回答2:
This has to do with how Exceptions are handled in Asynchronous tasks.
Per Microsoft's website (https://msdn.microsoft.com/en-us/magazine/jj991977.aspx):
When an exception is thrown out of an async Task or async Task method, that exception is captured and placed on the Task object
This means that when you are throwing an exception in your asynchronous method, it should be getting that exception and placing it on the task object itself. It even goes on to give an example on the website that if there is a Task object returned, the exception would never be thrown in the main thread because it is put on the Task object.
It sounds like you will need to check the Task object to see if it is valid or contains an exception.
回答3:
You can mark your ThreadTest function as async and use: await Faulty(); inside try-catch block than you will be able to catch an exception.
来源:https://stackoverflow.com/questions/36771209/c-sharp-error-propagation-with-continuewith