8

Platform: Silverlight 4, .NET 4

With the .NET 4.5 Developer preview the RegEx class has been enhanced to allow setting of a Timeout value which would prevent the RegEx engine from hanging the UI if there are issues with the pattern matching.

Requesting suggestions to implement a similar functionality in a .NET 4 Silverlight application.

Thanks in advance.

Vaibhav
  • 1,156
  • 2
  • 12
  • 27
  • See also this answer using Task: http://stackoverflow.com/a/13526507/492 ... I don't know if it works in Silverlight though. – CAD bloke Jul 08 '14 at 21:35

4 Answers4

13

Generic example:

public static R WithTimeout<R>(Func<R> proc, int duration)
{
  var wh = proc.BeginInvoke(null, null);

  if (wh.AsyncWaitHandle.WaitOne(duration))
  {
    return proc.EndInvoke(wh);
  }

  throw new TimeOutException();
}

Usage:

var r = WithTimeout(() => regex.Match(foo), 1000);

Update:

As pointed out by Christian.K, the async thread will still continue running.

Here is one where the thread will terminate:

public static R WithTimeout<R>(Func<R> proc, int duration)
{
  var reset = new AutoResetEvent(false);
  var r = default(R);
  Exception ex = null;

  var t = new Thread(() =>
  {
    try
    {
      r = proc();
    }
    catch (Exception e)
    {
      ex = e;
    }
    reset.Set();
  });

  t.Start();

  // not sure if this is really needed in general
  while (t.ThreadState != ThreadState.Running)
  {
    Thread.Sleep(0);
  }

  if (!reset.WaitOne(duration))
  {
    t.Abort();
    throw new TimeoutException();
  }

  if (ex != null)
  {
    throw ex;
  }

  return r;
}

Update:

Fixed above snippet to deal with exceptions correctly.

leppie
  • 115,091
  • 17
  • 196
  • 297
  • 3
    But won't this (also) continue running in the background if a timeout occurs? – Christian.K Feb 28 '12 at 05:23
  • @Christian.K: I thought so, but it seems you are correct! Thanks :) Back to drawing board for this one! – leppie Feb 28 '12 at 05:43
  • 2
    Nicely done. A couple notes about this though. Calling `t.Abort()` in your example will cause a MethodAccessException as explained here... http://msdn.microsoft.com/en-us/library/ty8d3wta(v=vs.95).aspx The only case when it won't is when it's a Silverlight 5 application running with elevated trust as explained here... http://msdn.microsoft.com/en-us/library/ee721083(v=vs.95).aspx I was hoping that this would also work with Silverlight 4 in elevated trust, but it appears that it doesn't. I get the same exception I do in partial trust. – Steve Wortham Jul 06 '12 at 14:15
  • 1
    @SteveWortham: Thanks for that helpful information :) So I guess there is no way to abort a thread in Silverlight under partial trust? Seems counter-intuitive. – leppie Jul 06 '12 at 15:40
  • Nope. There's no way I'm aware of. Your code should work for Silverlight 5 elevated-trust though. For this particular example, it's like we need .NET 4.5's Regex class compiled for Silverlight so we gain access to the Timeout property. – Steve Wortham Jul 06 '12 at 15:48
  • Well `Thread.Join` just waits for the specified timeout and then checks to see if the Thread is still running. It won't cancel or abort the thread, which is what we need here. – Steve Wortham Jul 06 '12 at 15:53
  • I've never tried this, but it looks like it might be possible to disassemble and reassemble a .NET assembly to be used in Silverlight... http://www.netfxharmonics.com/2008/12/Reusing-NET-Assemblies-in-Silverlight – Steve Wortham Jul 06 '12 at 16:25
  • @SteveWortham: That seems like more pain than it's worth ;p – leppie Jul 06 '12 at 16:59
3

It is not that simple - but it can be done using two threads with the first doing the regex, the second killing the first thread if itruns too long. This is problematic in itself, though.

TomTom
  • 61,059
  • 10
  • 88
  • 148
  • 2
    +1 for acknowledging "This is problematic in itself, though". :-) – Christian.K Feb 27 '12 at 06:37
  • Sadly it is the only w o do that without the regex method supporting it ;( It is a very good thing to have, though, but... well... .NET 4.5 here I come... this week. – TomTom Feb 27 '12 at 06:52
2

I re-implemented the above code changing it in a way that I belive is more reliable.

    /// <summary>
    /// Executes function proc on a separate thread respecting the given timeout value.
    /// </summary>
    /// <typeparam name="R"></typeparam>
    /// <param name="proc">The function to execute.</param>
    /// <param name="timeout">The timeout duration.</param>
    /// <returns></returns>
    public static R ExecuteWithTimeout<R>(Func<R> proc, TimeSpan timeout) {
        var r = default(R); // init default return value
        Exception ex = null; // records inter-thread exception

        // define a thread to wrap 'proc'
        var t = new Thread(() => {
            try {
                r = proc();
                }
            catch (Exception e) {
                // this can get set to ThreadAbortException
                ex = e;

                Debug.WriteLine("Exception hit");

                }
            });

        t.Start(); // start running 'proc' thread wrapper
        // from docs: "The Start method does not return until the new thread has started running."

        if (t.Join(timeout) == false) {
            t.Abort(); // die evil thread!
            // Abort raises the ThreadAbortException
            int i = 0;
            while ((t.Join(1) == false) && (i < 20)) { // 20 ms wait possible here
                i++;
                }
            if (i >= 20) {
                // we didn't abort, might want to log this or take some other action
                // this can happen if you are doing something indefinitely hinky in a
                // finally block (cause the finally be will executed before the Abort 
                // completes.
                Debug.WriteLine("Abort didn't work as expected");
                }
            }

        if (ex != null) {
            throw ex; // oops
            }
        return r; // ah!
        } 
Dweeberly
  • 4,668
  • 2
  • 22
  • 41
1

The standard way to get a timeout on something that doesn't already come with the feature, is to simply start whatever you want to process on a separate thread, and then in your main thread, you use Thread.Join with the appropriate timeout.

Cine
  • 4,255
  • 26
  • 46
  • 2
    But keep in mind that the other thread does not stop running, just because your join-timeout expired. If one can live with that, fine. However, depending on the frequency regex threads my be started (impatient user clicking a link/button), that may lead to a whole bunch of running threads and used resources. Not to speak of run-away regexes "burning" CPU in the background. – Christian.K Feb 27 '12 at 06:35
  • Obviously, if you don't want it to continue, then you add a Thread.Abort after the timeout... But in some cases you might just want to pop up a warning on the GUI saying "This is taking longer than expected" – Cine Feb 27 '12 at 07:11
  • Yes, but that "obvious" part is what the new Regex-timeout support in .NET 4.5 does - and the hard one (`Thread.Abort` has it's own set of issues). – Christian.K Feb 27 '12 at 07:14