11

I have a function that calls out a read or write request on a serial port and then returns the value that was read. I am using Commstudio express (I'm implementing a class from Commstudio) , but it's timeout features don't appear to work at all, so I'm trying to implement my own timeout. Currently I have a timer that is set upon request to read or write to the port, and if the timer goes off, the callback closes the connection causing an exception. I tried to have the callback of the timer throw an exception, but the exception needs to be propagated up through the thread that was calling the original read/write function, so in this way, it works, but I feel like it's messy and there must be a better way to do what I want.

MGSoto
  • 2,293
  • 5
  • 21
  • 29

5 Answers5

34

Here is a generic solution that allows you to wrap any method in a timeout:

http://kossovsky.net/index.php/2009/07/csharp-how-to-limit-method-execution-time/

It uses the useful Thread.Join overload that accepts a timeout in milliseconds rather than manually using timers. The only thing I would do differently is swap the success flag and result value to match the TryParse pattern, as follows:

public static T Execute<T>(Func<T> func, int timeout)
{
    T result;
    TryExecute(func, timeout, out result);
    return result;
}

public static bool TryExecute<T>(Func<T> func, int timeout, out T result)
{
    var t = default(T);
    var thread = new Thread(() => t = func());
    thread.Start();
    var completed = thread.Join(timeout);
    if (!completed) thread.Abort();
    result = t;
    return completed;
}

And this is how you would use it:

var func = new Func<string>(() =>
    {
        Thread.Sleep(200);
        return "success";
    });
string result;
Debug.Assert(!TryExecute(func, 100, out result));
Debug.Assert(result == null);
Debug.Assert(TryExecute(func, 300, out result));
Debug.Assert(result == "success");

You could also add overloads that accept Action instead of Func if you want to execute a method that doesn't return a value.

Community
  • 1
  • 1
Nathan Baulch
  • 20,233
  • 5
  • 52
  • 56
  • What about this http://stackoverflow.com/a/990566/206730? More in http://stackoverflow.com/questions/299198/implement-c-sharp-generic-timeout – Kiquenet Apr 12 '13 at 09:08
  • 1
    **Important:** before `thread.Start()`, you should set `thread.IsBackground = true` otherwise the thread keeps running after timeout, and after closing the application, it will stay running in Task Manager. – Adam Szabo Sep 03 '13 at 11:54
  • @user2270404 The thread gets aborted two lines of code after it is started. Unless the thread catches the ThreadAbortException and calls `ResetAbort`, or something throws an exception, I don't see how the thread could keep running. That said, calling `Join` with a timeout less than -1 **will** [throw an exception](http://msdn.microsoft.com/en-us/library/6b1kkss0.aspx), so anybody copying this code should validate input (and if that doesn't feel like enough set `IsBackground` too). – Carl Walsh Dec 11 '13 at 21:28
  • I think it's better to use Task.Delay() than Thread.Sleep() due to performance issues. – konkri Dec 05 '22 at 09:49
2

Sounds like you're doing a blocking read/write. What you want to do is a non-blocking read/write.

There is probably a way to tell the com port that you're wanting non- blocking.

Are you sure the timeouts are not working with commstudio? maybe you have to do something special to initialise them.

In any case, you want to read as much data as possible and if none is available time out (depending on what the value of the time out is). You'll want to keep looping while no data available and no error and then return a time out condition if there wasn't anything available.

Make your read function return an integer. negative values = error value e.g. -1 = timeout, positive number of bytes read... at least thats the way I'd do it.

hookenz
  • 36,432
  • 45
  • 177
  • 286
  • I'm pretty sure the timeout is not working, the DeviceError event is never raised and nothing happens even leaving it to run for an hour. My read function is a simple overload that adds some custom logging, but in the end, it's a base.Read(). – MGSoto Sep 03 '09 at 07:01
1

You can create an extension method that takes the Task<T> and a defaultValue of type T as input. Here is my implementation.

 public static class Helpers
    {
        public static Task<T> SetTimeout<T>(this Task<T> task, T defaultValue, int timeoutInMilliseconds = 1000)
        {
            var timeout = Task.Delay(timeoutInMilliseconds);
            Task.WaitAny(task, timeout);

            if (!task.IsCompleted)
                return Task.FromResult(defaultValue);

            return task;
        }
    }

Here is a usage example:

var cars = await _myService.GetCars().SetTimeout(new List<string>() { "Toyota", "Nissan" });
konkri
  • 186
  • 2
  • 12
0

In case someone wants to do this in VB.Net, don't listen to those who say it can't be done! You may need to alter your generic parameters to suit your use case.

  Public Shared Function Execute(Of I, R)(Func As Func(Of I, R), Input As I, TimeOut As Integer) As R
    Dim Result As R
    TryExecute(Func, Input, TimeOut, Result)
    Return Result
  End Function

  Public Shared Function TryExecute(Of I, R)(Func As Func(Of I, R), Input As I, TimeOut As Integer, ByRef Result As R) As Boolean
    Dim OutParam As R = Nothing
    Dim Thread As New System.Threading.Thread(Sub() InlineAssignHelper(OutParam, Func(Input)))
    Thread.IsBackground = True
    Thread.Start()
    Dim Completed As Boolean = Thread.Join(TimeOut)
    If Not Completed Then Thread.Abort()
    Result = OutParam
    Return Completed
  End Function

  Private Shared Function InlineAssignHelper(Of T)(ByRef Target As T, ByVal Value As T) As T
    Target = Value
    Return Value
  End Function

And an example of how to use it (mine was with Regex.Match, which sometimes goes off into never never land if the patterns contains too many wild cards:

  Public Function Match(Input As String) As Match
    If Regex Is Nothing Then Return Nothing
    Dim RegexMatch As System.Text.RegularExpressions.Match = Nothing
    Dim Func As New Func(Of String, System.Text.RegularExpressions.Match)(Function(x As String) Regex.Match(x))
    If Runtime.TryExecute(Of String, System.Text.RegularExpressions.Match)(Func, Input, 2000, RegexMatch) Then
      Return (New Match(Me, Regex.Match(Input), Input))
    Else
      Return Nothing
    End If
  End Function
toddmo
  • 20,682
  • 14
  • 97
  • 107
0

For the comport you could just test if there is anything available and then do a read instead of doing a blocking read without knowing there is something yet. Something like:

Int32 timeout=1000;
String result = String.Empty';
while (timeout!=0) {
  if (Serial.BytesToRead>0) {
    while (Serial.BytesToRead>0) {
      result+=Serial.ReadChar();
    }
    break;
  }
  Thread.Sleep(1);
  timeout--;
}
wvd_vegt
  • 326
  • 2
  • 5