-3

I try to create a sudoku resolver. So, in the business logic, we have a game board representing the sudoku. In the business logic, we use a method that does a backtracking processing to resolve Sudoku.

I have the following error with this line in NotifyChange method in Form1.cs : tableLayoutPanel1.Controls.Add(label, column, line);

Inter-thread operation no valid control accessed from a thread other than the thread it was created

SudokuListener.cs :

interface SudokuListener
{
    void NotifyChange(Int32 line, Int32 column, Token token);
}

Form1 :

public partial class Form1 : Form, SudokuListener
{
    delegate void Del(SudokuListener sudokuListener);

    void SudokuListener.NotifyChange(int line, int column, Token token)
    {
        Label label = new Label();

        if (token.IsMarked())
        {
            label.ForeColor = Color.Blue;

        }
        label.Text = ""+token.GetNbr();

        tableLayoutPanel1.Controls.Add(label, column, line);
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        Del del = new Del(SudokuThread.Start);
        BackgroundWorker backgroundWorker = new BackgroundWorker();
        backgroundWorker.WorkerReportsProgress = true;
        backgroundWorker.WorkerSupportsCancellation = true;
        backgroundWorker.DoWork += new DoWorkEventHandler(brol);
        backgroundWorker.RunWorkerAsync();
    }

    private void brol(object sender, DoWorkEventArgs e)
    {
        SudokuThread.Start(this);
    }
}

SudokuThread.cs :

class SudokuThread
{
    public static void Start(SudokuListener listener)
    {
        SudokuGameBoard sudokuGameBoard = new SudokuGameBoard();
        Sudoku sudoku = new Sudoku();
        sudokuGameBoard.AddListeners(listener);
        sudoku.Load(sudokuGameBoard);
        sudoku.FindSolution(sudokuGameBoard, 0);
    }
}

Sudoku.cs :

class Sudoku
{
    public void Load(SudokuGameBoard gameBoard)
    {
        gameBoard.SetToken(0, 0, new Token(0, false));
        gameBoard.SetToken(0, 1, new Token(0, false));
        gameBoard.SetToken(0, 2, new Token(0, false));
        gameBoard.SetToken(0, 3, new Token(0, false));
        gameBoard.SetToken(0, 4, new Token(0, false));
        gameBoard.SetToken(0, 5, new Token(0, false));
        gameBoard.SetToken(0, 6, new Token(0, false));
        gameBoard.SetToken(0, 7, new Token(0, false));
        gameBoard.SetToken(0, 8, new Token(0, false));

        gameBoard.SetToken(1, 0, new Token(0, false));
        gameBoard.SetToken(1, 1, new Token(0, false));
        gameBoard.SetToken(1, 2, new Token(7, true));
        gameBoard.SetToken(1, 3, new Token(8, true));
        //rest of the code
    }

    private void PutToken(SudokuGameBoard gameBoard, Int32 line, Int32 column, Token token)
    {
        if ( !gameBoard.GetToken(line,column).IsMarked())
        {
            gameBoard.SetToken(line, column, token);
        }
    }
    public Boolean FindSolution(SudokuGameBoard gameBoard, Int32 position)
    {
        Boolean finish = false;
        Int32 currentNbr = 0;

        do {
            currentNbr++;

            if ( IsPossible(gameBoard, position / 9, position % 9, currentNbr) )
            {
                PutToken(gameBoard, position / 9, position % 9, new Token(currentNbr));
                //rest of the code
            }
       }while (currentNbr != 9 && !finish);
    }
}

SudokuGameBoard.cs :

class SudokuGameBoard
{
    public void SetToken(Int32 line, Int32 column, Token newToken)
    {
        array[line, column] = newToken;

        NotifyListeners(line, column, newToken);
    }

    private void NotifyListeners(Int32 line, Int32 column, Token token)
    {
        for (Int32 i = 0; i < listeners.Count; i++)
        {
            listeners[i].NotifyChange(line, column, token);
        }
    }
  }
user2274060
  • 896
  • 5
  • 18
  • 35
  • 1
    Just [Invoke](https://msdn.microsoft.com/en-us/library/system.windows.forms.control.invoke(v=vs.110).aspx) or [BeginInvoke](https://msdn.microsoft.com/en-us/library/system.windows.forms.control.begininvoke(v=vs.110).aspx). – Theraot May 12 '17 at 08:56
  • Possible duplicate of [Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on](http://stackoverflow.com/questions/142003/cross-thread-operation-not-valid-control-accessed-from-a-thread-other-than-the) –  May 12 '17 at 08:56
  • No windowing system (Windows, Linux, Mac) allows you to modify the UI from a non-UI thread. – Panagiotis Kanavos May 12 '17 at 09:00
  • Don't use a BackgroundWorker, it's obsolete. Whatever it does, you can du with the TPL library, Task.Run async/await. Progress reporting is available through the IProgress and Progress classes. Your code is overcomplicated, when it tries to do something relatively simple – Panagiotis Kanavos May 12 '17 at 09:03
  • Don't use Invoke or BeginInvoke if you target .NET 4.5+. Use `await` to await the completion of a background operation and then update the UI – Panagiotis Kanavos May 12 '17 at 09:04
  • I created a backtracking algorithm to test all the possibilities for my sudoku grid. When the backtracking algorithm modifies a cell in my grid, I would like to see the changes in my TableLayoutPanel in my view. Please help me – user2274060 May 12 '17 at 09:06
  • How to access to a Control from a thread other than the thread it was created ? – user2274060 May 12 '17 at 09:14

1 Answers1

1

The error itself is caused because you can't modify the UI from a non-UI thread. This isn't a .NET or Windows limitation. No windowing system allows a background thread to modify the UI.

Your code is overcomplicated too, using delegates that act as events and a BackgroundWorker. The BGW is obsolete since anything it does can be done just as easily with tasks and the IProgress interface for reporting. Tasks allow you to easily perform multiple background operations, or get back to the UI thread after a background operation. Both of these things are very hard with a BGW.

Your code could be as simple as the following code:

A class to report board status

class BoardStatus
{
    public int Line {get;}
    public int Column {get;}
    public Token Token {get;}
    public BoardStatus(int line, int column,Token token)
    {
        Line=line;
        Column=column;
        this.Token=token;
    }
}

And the following code in the form:

void UpdateStatus(BoardStatus status)
{
    var color = status.token.IsMarked() ? Color.Blue : Color.Black;
    var label = new Label
                {
                    Text = token.GetNbr().ToString(),
                    ForeColor = color;
                };

    tableLayoutPanel1.Controls.Add(label, status.column, status.line);
}

public async void Start_Click(object sender, EventArgs args)
{
    TotalStatusLabel.Text="Starting";
    var progress=new Progress<TokenProgress>(UpdateStatus);

    //Run in the background without blocking
    await Task.Run(()=>Solve(progress));

    //We are back in the UI
    TotalStatusLabel.Text="Finished";
}

private void Solve(IProgress<TokenStatus> progress)
{
    SudokuGameBoard sudokuGameBoard = new SudokuGameBoard(progress);
    Sudoku sudoku = new Sudoku();
    sudoku.Load(sudokuGameBoard);
    sudoku.FindSolution(sudokuGameBoard, 0);
}

With the board reporting progress through the IProgress interface:

class SudokuGameBoard
{
    IProgress<BoardStatus> _progress;
    public SudokuGameBoard(IProgress<BoardStatus> progress)
    {
        _progress=progress;
    }
    public void SetToken(Int32 line, Int32 column, Token newToken)
    {
        array[line, column] = newToken;

        _progress.Report(new BoardStatus(line, column, newToken));
    } 
}
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236