C# 如何在处理异常时异步延迟所有未来调用
我有一个使用有限资源的异步方法。如果资源变得不可用,我想延迟以后的调用,直到资源再次可用 基本上,如果在访问有限的资源时发生异常,单个线程将通过延迟所有捕获异常的线程和调用该方法的其他线程来处理错误。C# 如何在处理异常时异步延迟所有未来调用,c#,.net,multithreading,asynchronous,async-await,C#,.net,Multithreading,Asynchronous,Async Await,我有一个使用有限资源的异步方法。如果资源变得不可用,我想延迟以后的调用,直到资源再次可用 基本上,如果在访问有限的资源时发生异常,单个线程将通过延迟所有捕获异常的线程和调用该方法的其他线程来处理错误。5秒后,线程将重试访问资源。这有点像节流 我已经通过滥用TaskCompletionSource和一个信号量lim实现了这一点。它似乎起作用了。是否可以将此改进为更少。。。哈奇 // Use SemaphoreSlim to make sure only one thread handles an
5
秒后,线程将重试访问资源。这有点像节流
我已经通过滥用TaskCompletionSource
和一个信号量lim
实现了这一点。它似乎起作用了。是否可以将此改进为更少。。。哈奇
// Use SemaphoreSlim to make sure only one thread handles an error at a time.
private static readonly SemaphoreSlim mySemaphore = new SemaphoreSlim(1);
// Use TaskCompletionSource as a flag to delay threads while an error is handled.
private static volatile TaskCompletionSource<bool> myFlag;
static MyClass()
{
myFlag = new TaskCompletionSource<bool>();
myFlag.SetResult(false); // At startup there is no error being handled.
}
public async Task DoSomethingAsync()
{
while (true)
{
await myFlag.Task; // Wait if an error is being handled.
try
{
await ... // Call some asynchronous operations here that can cause errors.
return;
}
catch
{
await mySemaphore.WaitAsync(); // Wait so only one thread handles an error.
bool wasHandled = await myFlag.Task; // Wait and check if error was handled.
if (wasHandled == false)
{
// Reset TaskCompletionSource so error handling on other threads waits.
myFlag = new TaskCompletionSource<bool>();
mySemaphore.Release();
await Task.Delay(5000); // "Handle" the error by waiting 5 seconds.
myFlag.SetResult(true); // Notify waiting threads an error was handled.
// Reset TaskCompletionSource
myFlag = new TaskCompletionSource<bool>();
myFlag.SetResult(false);
}
else // (wasHandled == true)
{
mySemaphore.Release(); // Move along, nothing to see here.
}
}
}
}
//使用SemaphoreSlim确保一次只有一个线程处理错误。
私有静态只读信号量lim mySemaphore=新信号量lim(1);
//将TaskCompletionSource用作处理错误时延迟线程的标志。
私有静态易失性TaskCompletionSource myFlag;
静态MyClass()
{
myFlag=新任务完成源();
myFlag.SetResult(false);//启动时未处理任何错误。
}
公共异步任务DoSomethingAsync()
{
while(true)
{
Wait myFlag.Task;//如果正在处理错误,请等待。
尝试
{
wait…//在此处调用一些可能导致错误的异步操作。
返回;
}
抓住
{
Wait mySemaphore.WaitAsync();//等待,以便只有一个线程处理错误。
bool wasHandled=Wait myFlag.Task;//等待并检查是否已处理错误。
if(wasHandled==假)
{
//重置TaskCompletionSource,以便等待其他线程上的错误处理。
myFlag=新任务完成源();
mySemaphore.Release();
等待任务。延迟(5000);//等待5秒以“处理”错误。
myFlag.SetResult(true);//通知等待的线程已处理错误。
//重置TaskCompletionSource
myFlag=新任务完成源();
myFlag.SetResult(false);
}
else/(WashHandled==真)
{
mySemaphore.Release();//继续前进,这里没有什么可看的。
}
}
}
}
为了阐明我认为应该改进的原因:我正在使用TaskCompletionSource
创建一个等待的布尔状态,要重置它,我必须每次实例化一个新的TaskCompletionSource
。我认为这不是TaskCompletionSource
的预期用途
我已经看过了
ManualResetEvent
和AutoResetEvent
,因为它们似乎能满足我的需要,但它们不提供异步功能。如果我正确理解您的情况:消费者-->您的服务-->另一个服务/资源
您不应该在您的服务中实施退避策略,因为终端客户机正在排队等候。让客户端实现回退,您的服务只需要实现一个断路器,甚至是可选的
所以
在服务方面:
你应该有一个私人工作者方法(例如,DoSomething
),它只负责“做某事”。它应该包装在一个公共方法中,该方法实现了
在消费者方面:
您的“服务”(公共方法)的使用者应该实现。这里为您提供一些想法和代码示例。然而,您确实应该研究断路器模式,这样您就可以自己实现它或获得工作实现 正如您现在所做的,重置事件实现可能适合您,因为它涵盖了逐个处理异常的情况(因为它只允许一个线程通过它),并涵盖了在正常执行的情况下运行所有线程的情况:
var manual = new ManualResetEventSlim(true);
var auto = new AutoResetEvent(true);
while (true)
{
// check for normal work
manual.Wait();
try
{
}
catch
{
auto.Wait();
// only one thread here
// stop all the worker threads
manual.Reset();
// handling here
// start all the workers
manual.Set();
// start exception handlers
auto.Set();
}
}
请注意,您可以模拟(正如您已经做的那样)。您应该使用SemaforSlim(1,1)
构造函数,以便1
线程能够继续,并且semafor是初始设置的
不过,这个版本是非异步的,所以您在这里有一些选择
产生执行时间,或者,如果您想设置重试超时,只需使用from TPL:
while (!manual.Wait(SMALL_TIMEOUT_FOR_WAITING)
{
// Thread.Yield();
await Task.Delay(LARGE_RETRY_TIMEOUT);
}
您还应该注意到,通常的做法是为循环和面向任务的方法创建一个新的应用程序,以便在停止整个系统的情况下更好地管理代码。这是一个可怕的错误idea@HristoYankov这就是我在这里发帖的原因。想提出一个更好的主意吗?有很多这样的库,比如Polly()。似乎他们的断路器实现对您的场景很有用。@彼得伯恩斯我无法与波利一起实现我的目标。不过图书馆很棒!“指数退避策略”页面指出:“所述内容和技术已过时,不再进行维护。”。你确定我应该使用它吗?我链接到的应用程序块可能已过时。我应该链接到指数退避策略模式的抽象描述。它仍然有效。我知道我需要分离关注点。但是,如何使用C#的
async
-await
实现这些模式呢?您为“指数退避策略”提供的页面在其示例中使用了Thread.Sleep
,这正是我需要避免的。感谢您提到Stephen Cleary的AsyncEx库。我明天会试试。如何使用AutoResetEvent
确保只有一个线程进行处理?在您的示例中,每次处理一个线程。我不会等待等待线程在