J'ai objet obj
qui est un composant tiers,
// this could take more than 30 seconds
int result = obj.PerformInitTransaction();
Je ne sais pas ce qui se passe à l'intérieur. Ce que je sais, c'est que si cela prend plus de temps, c'est un échec.
comment configurer un mécanisme de temporisation pour cette opération, afin que si cela prend plus de 30 secondes, je lance simplement MoreThan30SecondsException
?
Vous pouvez exécuter l'opération dans un thread séparé, puis définir un délai d'attente pour l'opération de jointure de thread:
using System.Threading;
class Program {
static void DoSomething() {
try {
// your call here...
obj.PerformInitTransaction();
} catch (ThreadAbortException) {
// cleanup code, if needed...
}
}
public static void Main(params string[] args) {
Thread t = new Thread(DoSomething);
t.Start();
if (!t.Join(TimeSpan.FromSeconds(30))) {
t.Abort();
throw new Exception("More than 30 secs.");
}
}
}
Plus simplement en utilisant Task.Wait(TimeSpan)
:
using System.Threading.Tasks;
var task = Task.Run(() => obj.PerformInitTransaction());
if (task.Wait(TimeSpan.FromSeconds(30)))
return task.Result;
else
throw new Exception("Timed out");
Si vous ne voulez pas bloquer le thread principal, vous pouvez utiliser un System.Threading.Timer :
private Thread _thread;
void Main(string[] args)
{
_thread = new ThreadStart(ThreadEntry);
_thread.Start();
Timer timer = new Timer(Timeout,null,30000,Timeout.Infinite);
}
void ThreadEntry()
{
int result = obj.PerformInitTransaction();
}
void TimeOut(object state)
{
// Abort the thread - see the comments
_thread.Abort();
throw new ItTimedOutException();
}
Jon Skeet a moins de force (Fermer les fils de travail avec élégance) pour arrêter le fil que d’abandonner.
Cependant, étant donné que vous ne maîtrisez pas les opérations que PerformInitTransaction()
effectue, vous ne pouvez pas faire grand chose à partir du moment où Abort échoue et laisse l'objet dans un état non valide. Comme mentionné, si vous êtes en mesure de nettoyer tout ce qui a été abandonné par la PerformInitTransaction
, vous pouvez le faire en interceptant la ThreadAbortException
, bien que, comme c'est un appel de tierce partie, il faudra deviner l'état dans lequel vous avez laissé sa méthode.
La PerformInitTransaction
devrait vraiment être celle qui fournit le délai d'attente.
Vous devez faire attention à abandonner une opération comme celle-ci, d'autant plus que c'est dans un composant tiers que vous n'avez (éventuellement) pas accès au code à modifier.
Si vous abandonnez l'opération, vous ne saurez pas dans quel état vous avez laissé la classe sous-jacente. Par exemple, il est possible qu'elle ait acquis un verrou et que votre à propos ait empêché son verrouillage. Même si vous détruisez l'objet après l'abandon de l'opération, il est possible qu'il ait modifié un état global et que, par conséquent, vous ne puissiez pas créer de manière fiable une nouvelle instance sans redémarrage.
Vous pouvez envisager d'appeler la méthode dans un thread et, une fois le délai expiré, abandonner le thread et lever l'exception. En outre, vous devrez gérer l'exception ThreadBorted dans ce cas.
c'est ce que je voudrais utiliser. fonctionne de la même manière qu'un délai d'attente javascript.
public class Toolz {
public static System.Threading.Tasks.Task<object> SetTimeout(Func<object> func, int secs) {
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(secs));
return System.Threading.Tasks.Task.Run(() => func());
}
}
class Program {
static void Main(string[] args) {
Console.WriteLine(DateTime.Now);
Toolz.SetTimeout(() => {
Console.WriteLine(DateTime.Now);
return "";
}, 10);
}
}
Il existe un bel exemple d’une solution générique utilisant une classe d’aide ici .
Il utilise le délégué Action pour éviter la création/destruction de threads présentée dans l'exemple précédent.
J'espère que ça aide.
Je viens de rencontrer cela dans une application .NET 4.0 (pas d'accès à Task.Run
, Task.Delay
, etc.). Si vous voulez bien excuser la dernière ligne (qui est la partie setTimeout), elle est assez concise.
int sleepTime = 10000;
Action myAction = () => {
// my awesome cross-thread update code
this.BackColor = Color.Red;
};
new System.Threading.Thread(() => { System.Threading.Thread.Sleep(sleepTime); if (InvokeRequired) myAction(); else myAction(); }).Start();
Voici deux implémentations qui génèrent également toute exception se produisant dans la tâche interne.
Pour les actions (pas de valeur de retour):
public static bool DoWithTimeout(Action action, int timeout)
{
Exception ex = null;
CancellationTokenSource cts = new CancellationTokenSource();
Task task = Task.Run(() =>
{
try
{
using (cts.Token.Register(Thread.CurrentThread.Abort))
{
action();
}
}
catch (Exception e)
{
if (!(e is ThreadAbortException))
ex = e;
}
}, cts.Token);
bool done = task.Wait(timeout);
if (ex != null)
throw ex;
if (!done)
cts.Cancel();
return done;
}
Pour les fonctions (avec une valeur de retour):
public static bool DoWithTimeout<T>(Func<T> func, int timeout, out T result)
{
Exception ex = null;
result = default(T);
T res = default(T);
CancellationTokenSource cts = new CancellationTokenSource();
Task task = Task.Run(() =>
{
try
{
using (cts.Token.Register(Thread.CurrentThread.Abort))
{
res = func();
}
}
catch (Exception e)
{
if (!(e is ThreadAbortException))
ex = e;
}
}, cts.Token);
bool done = task.Wait(timeout);
if (ex != null)
throw ex;
if (done)
result = res;
else
cts.Cancel();
return done;
}
Je pense que c'est le plus simple de tous -
using System.Threading.Tasks;
var timeToWait = 30000; //ms
Task.Run(async () =>
{
await Task.Delay(timeToWait);
//do your timed task i.e. --
int result = obj.PerformInitTransaction();
});