Unresponsive Windows Form

28 views
Skip to first unread message

Terry Aney

unread,
Jun 13, 2024, 5:04:14 PMJun 13
to Excel-DNA
I'm having a problem with a Windows Form not being responsive.  Admittedly my Windows Forms experience is limited and I've played around with sprinkling '.ConfigureAwait( false )' on some of my 'await' calls with no success.

I have a ribbon button that when pressed, executes some code (removed for brevity below) inside a `QueueAsMacro` context.  Then when ready, I need to run some async code and as I understand it, I use 'Task.Run()' to run on a new thread to support async code safely.  That creates a form and awaits the 'ProcessAsync' method.  That method accepts an async callback that simply awaits some 'Delay' calls and tries to increment the status and progress.  My 'Progress<T>' delegates are never invoked and the form is completely unresponsive.

Below is my code.  Does anyone see anything obviously wrong that I'm doing?

// My ribbon handler, when I click a button that has tag of CalcEngineUtilities_LocalBatchCalc
public void Ribbon_OnAction( IRibbonControl control )
{
var tag = control.Tag;

try
{
var mi = typeof( Ribbon ).GetMethod( tag );

mi!.Invoke( this, new object[] { control } );
}
catch ( Exception ex )
{
ShowException( ex, $"Ribbon_OnAction {tag}" );
}
finally
{
application.Cursor = MSExcel.XlMousePointer.xlDefault;
}
}

public void CalcEngineUtilities_LocalBatchCalc( IRibbonControl _ )
{
ExcelAsyncUtil.QueueAsMacro( () =>
{
// Removed code for brevity...

Task.Run( async () =>
{
try
{
using var processing = new Processing( "Local Batch Calculation", 1, 10, null );

var result = await processing.ProcessAsync( async ( currentProgress, currentStatus, cancellationToken ) =>
{
currentStatus.Report( "Starting batch calculations..." );

for ( var i = 0; i < 10; i++ )
{
await Task.Delay( 1000, cancellationToken ).ConfigureAwait( false );
currentProgress.Report( -1 );
}

currentStatus.Report( "Finished batch calculations" );
} );
}
catch ( Exception ex )
{
ExcelAsyncUtil.QueueAsMacro( () => MessageBox.Show( ex.Message ) );
}
} );
} );
}

My 'Processing' form code...

internal partial class Processing : Form
{
private readonly JsonObject windowConfiguration;
private readonly CancellationTokenSource cancellationTokenSource = new ();
private int currentProgressValue;

public Processing( string title, int progressSizeReductionFactor, int maximum, JsonObject? windowConfiguration )
{
InitializeComponent();
Text = title;
this.windowConfiguration = windowConfiguration ?? new JsonObject();
progressBar.Maximum = maximum;
}

// http://stackoverflow.com/a/18033198/166231
public async Task<ProcessingInfo> ProcessAsync( ProcessAsyncHandler action )
{
var result = DialogResult.None;

Show();

try
{
var currentProgress = new Progress<int>( i =>
{
if ( i == -1 )
{
currentProgressValue++;
progressBar.Value = currentProgressValue;
}
else if ( i > currentProgressValue )
{
progressBar.Value = currentProgressValue = Math.Max( progressBar.Maximum, i );
}
} );
var currentStatus = new Progress<string>( s => processingLabel.Text = s );

await action( currentProgress, currentStatus, cancellationTokenSource.Token );

result = DialogResult.OK;
}
catch ( OperationCanceledException )
{
result = DialogResult.Cancel;
}
catch ( Exception ex )
{
ExcelDna.Logging.LogDisplay.RecordLine( $"Process Exception: {ex.Message}" );
ExcelDna.Logging.LogDisplay.RecordLine( $"StackTrace: {ex.StackTrace}" );
}

Close();

return new() { Result = result, WindowConfiguration = windowConfiguration };
}

private void Cancel_Click( object sender, EventArgs e )
{
cancel.Enabled = false;
cancellationTokenSource.Cancel();
}
}

Kedar Kulkarni

unread,
Jun 14, 2024, 9:50:09 AMJun 14
to Excel-DNA
In my opinion, the async await would be useful when we are trying to run background work.  I would just use Forms without any async/await or start in Task.Run. 

When we click on ribbon, the call itself is on Main thread, so we just continue to load form and show the form (without queue as macro) and start the background processing with task.run and any progress can be shown using control.invoke(). I am not an expert in threads / tasks but have always believed that accessing form/controls/excel objects from background (or from a thread that did not create it) would cause excel to remain in memory after close. Again many a times excel is a black-box or we don't know if any issues are caused by our addin or any other addin loaded in memory. So when you are on main thread, get your work done on it without giving opportunity to other app to use main thread. I assume the form is shown modal.May want to use Application.DoEvents() if needed to make it more responsive. (not sure if the best practice but excel would want to catchup on messages). (IMO - Not trying to imply that we should unnecessarily hold main thread but when we know we are already on main thread, queue as macro may be unnecessary.) .
Reply all
Reply to author
Forward
0 new messages