0

I am using ANTLR to parse some files. Each file defines a list of dependencies. As this may require a lot of large files to be loaded, I am trying to multithread this.

When running on one thread: An error is being thrown on the indicated line of VisitScope. I know this because it was failing here before I implemented multithreading.

When running on multiple threads: The program is exiting upon entering the ANTLR Lexer constructor (as indicated below). No catch clause or error logging is seen on the console. I am using NLog for logging (which according to this comment is thread safe).

I am using ANTLR 4.11.1 (JAR, targeting C#) and have not modified the parser or lexer in any way from the generated code.

Why is multithreading preventing me from stepping into the lexer constructor, and why is it also concealing the error logging?

   // The entry point for this process
   public Task<Scope> StartLoadingScope (Uri uri)
        {
            // Safety checks to prevent double loading

            Task<Scope> task = new Task<Scope>(() =>
            {
                return LoadScopeSync(uri);
            });

            task.Start();
            return task;
        }
    // Called from StartLoadingScope
    private Scope LoadScopeSync (Uri uri)
        {
            string text = // Get the text

            Scope scope = ParseScopeText(uri, text);

            return scope;
        }
    // Called from LoadScopeSync
    private Scope ParseScopeText (Uri source, string textIn)
        {
            try
            {
                // Setup ANTLR
                AntlrInputStream a4is = new AntlrInputStream(textIn);
                
                // Multithreaded program fails/exits upon entering this constructor
                // If I try to step into it with the Visual Studio debugger, the program
                // exits immediately
                CrimsonLexer lexer = new CrimsonLexer(a4is);
                CommonTokenStream cts = new CommonTokenStream(lexer);
                CrimsonParser parser = new CrimsonParser(cts);

                // Setup error handling
                string sourceName = $"{source}";
                lexer.AddErrorListener(new LexerErrorListener(sourceName));
                parser.ErrorHandler = new ParserErrorStrategy(sourceName);

                // Visit the scope
                CrimsonParser.ScopeContext cuCtx = parser.scope();
                ScopeVisitor visitor = new ScopeVisitor();
                Scope scope = visitor.VisitScope(cuCtx);

                return scope;
            }
            catch (Exception ex)
            {
                // This catch clause is never seen on the console
                LOGGER.Error("An error ocurred while parsing a scope");
                Console.WriteLine("An error ocurred while parsing a scope");
                throw new StatementParseException("A parser error occurred :(", ex);
            }
        }
    // Part of my ANTLR visitor (called indirectly via ParseScopeText)
    public override Scope VisitScope ([NotNull] CrimsonParser.ScopeContext context)
        {
            try
            {
                // Some things...

                // Visit imports
                IList<CrimsonParser.ImportUnitContext> importCtxs = context._imports;
                foreach (CrimsonParser.ImportUnitContext importCtx in importCtxs)
                {
                    // This line throws an exception
                    Uri uri = new Uri(importCtx.uri.Text);
                    FullNameCToken id = VisitFullName(importCtx.fullName());
                    ImportCStatement import = new ImportCStatement(uri, id);
                    scope.Imports.Add(id.ToString(), import);
                }

                // More things...
            }

            // This catch clause is also never seen on the console
            catch (Exception ex)
            {
                LOGGER.Error("VisitScope encountered an error!");
                Console.WriteLine("VisitScope encountered an error!");
                throw new StatementParseException("Unable to parse scope :(", ex);
            }
        }
Atom
  • 325
  • 1
  • 11

1 Answers1

0

After searching for information on handling errors in C# Tasks, I found the following sites of interest:

  1. SO - How to handle Task.Run exceptions
  2. SO - How to throw an exception in a C# thread
  3. Microsoft - Handling exceptions in async methods

My understanding from this is that exceptions will only propagate upwards (i.e. be thrown in the parent thread) if the task is awaited or Task.Wait() is called.

Hence, by calling Wait() on a task and surrounding it with a try-catch, you can catch exceptions thrown within the Task. This has solved the issue.

Atom
  • 325
  • 1
  • 11