Robben_Ford_Fan_boy
Robben_Ford_Fan_boy

Reputation: 8720

C# Prevent Call Hang with Child Thread CallBack

I have the following code that in a very specific scenario, will hang indefinitely:

connection = new OdbcConnection(connectionString);
connection.Open();

Unfortunately, the hang is outside my control in this very specific scenario.

So I would like to be able to handle this scenario and at the very least throw an exception.

I would like to spin of a child thread that will callback the main thread when it timeouts.

How would you do that - here is my attempt:

OdbcConnection connection = null;

var timeout = TimeSpan.FromSeconds(15);
var resetEvent = new ManualResetEvent(false);
bool exceptionThrown = false;

var connectionThread = new Thread(() =>
{
    try
    {
        connection = new OdbcConnection(connectionString);
        connection.Open();
    }
    catch(Exception e)
    {
        exceptionThrown = true;
    }
    finally
    {
        resetEvent.Set();
    }
});

connectionThread.Start();

var isOk = resetEvent.WaitOne(timeout);

if(exceptionThrown)
{
   throw now Exception("Exception connection to DB");
}

if (!isOk)
{
    connectionThread.Abort();
    const string messageFormat = "Timeout of {0} reached while creating OdbcConnection to {1}.";
    throw now Exception(string.Format(messageFormat, timeout, connectionString));
}

UPDATE: Here is my attempt using Task:

OdbcConnection connection = null;
var connectionTask = Task.Factory.StartNew(() =>
{
    connection = new OdbcConnection(connectionString);
    connection.Open();
    Thread.Sleep(3000);
});
try
{
    connectionTask.Wait(1000);       // Wait for 1 second.
}
catch (AggregateException ex)
{
    Console.WriteLine("Exception in connection");
}

bool completed = connectionTask.IsCompleted;
if(!completed)
{
    Console.WriteLine("Connection Timed-out");
}
else
{
    connection.DoSomething();
}

Upvotes: 3

Views: 608

Answers (5)

Baloo0ch
Baloo0ch

Reputation: 43

use this code instead for Task:

            OdbcConnection connection = null;
        var connectionTask = Task.Factory.StartNew(() =>
        {
            connection = new OdbcConnection(connectionString);
            connection.Open();
            Thread.Sleep(3000);
        });

        connectionTask.ContinueWith((tsk) => 
        {
            if(tsk.Exception==null && tsk.IsCompleted)
            {
                connection.DoSomething();
            }
            else
            {
                // here you can examine exception that thrown on task
                Console.WriteLine("Connection Timed-out");
            }
        });

Upvotes: 0

Julian
Julian

Reputation: 36710

Why not setting the Timeout property?

OdbcConnection.ConnectionTimeout = 15

The docs on MSDN state:

Unlike the .NET Framework data providers for SQL Server and OLE DB, the .NET Framework Data Provider for ODBC does not support setting this property as a connection string value, because it is not a valid ODBC connection keyword. To specify a connection time-out, set the ConnectionTimeout property before calling Open.

Update

I think the bug in Mometdb is that there are reading SQL_ATTR_CONNECTION_TIMEOUT (See source on GitHub), while it should be SQL_ATTR_LOGIN_TIMOUT, from MSDN:

To specify a connection time-out, set the ConnectionTimeout property before calling Open. This is equivalent to setting the ODBC SQLSetConnectAttr SQL_ATTR_LOGIN_TIMOUT attribute.

I think passing SQL_ATTR_CONNECTION_TIMEOUT=15 to the connectionstring should work.

Upvotes: 3

Simone Cifani
Simone Cifani

Reputation: 784

There are a fundamental difference between await/async and Task.Wait:

await (C# Reference) - An await expression does not block the thread on which it is executing. Instead, it causes the compiler to sign up the rest of the async method as a continuation on the awaited task. Control then returns to the caller of the async method. When the task completes, it invokes its continuation, and execution of the async method resumes where it left off.

Task.Wait, MSDN - Wait is a synchronization method that causes the calling thread to wait until the current task has completed. If the current task has not started execution, the Wait method attempts to remove the task from the scheduler and execute it inline on the current thread. If it is unable to do that, or if the current task has already started execution, it blocks the calling thread until the task completes.

From what I have understood, you have a UI application which, at a certain point, attempts to open an OdbcConnection. This operation requires some time during which the UI is blocked.
If I am correct, this is a very small example which doesn't block the UI:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        bool completed = await Task.Run(() => DoSomethingLong());

        if (!completed)
        {
            Console.WriteLine("Not completed");
        }
        else
        {
            Console.WriteLine("Completed");
        }
    }

    bool DoSomethingLong()
    {
        //This loop will take 15 seconds
        for (int i = 0; i < 30; i++)
        {
            if (i % 10 == 0)
                Console.WriteLine("<DoSomethingLong> - I am alive");

            Thread.Sleep(500);
        }

        return true;
    }
} 

Upvotes: 1

Naveen Sundar
Naveen Sundar

Reputation: 21

I am not sure what you exact requirement is. But if you are just trying to establish a ODBC connection async and you would like to do error handling then I think the below should work.

class Program
{
    static void Main(string[] args)
    {
        string connstring = "connstring");
        try
        {
            Program.Method(connstring);
        }
        catch(Exception ex)
        {
            var m = ex.Message;
        }
    }

    static async Task<int> Method(string connstring)
    {
        try
        {
            OdbcConnection conn = new OdbcConnection(connstring);

            await conn.OpenAsync();
        }
        catch (Exception ex)
        {
            var m = ex.Message;
        }
        return 1;
    }
}

Upvotes: 2

anserk
anserk

Reputation: 1320

I would do it this way:

  • Make the caller function async;
  • Declare a new Task where you do stuff with the connection;
  • Handle exception in the caller.

    static async void Main(string[] args)
    {
        try
        {
            await DoSomething("");
        }
        catch (TimeoutException ex)
        {
            // Handle Exception.
        }
        catch (SomeOtherException ex)
        {
            // Handle Exception.
        }
    }
    
    static Task DoSomething(string connectionString)
    {
        OdbcConnection connection = new OdbcConnection(connectionString);
        connection.Open();
        connection.DoSomethingElse();
    }
    

Upvotes: 1

Related Questions