1- public static partial class SpreadsheetCompare
1+ public static class SpreadsheetCompare
22{
33 static readonly string [ ] programFolders =
44 [
@@ -56,7 +56,7 @@ public static partial class SpreadsheetCompare
5656 return null ;
5757 }
5858
59- public static void Launch ( string path1 , string path2 , string ? exePath = null )
59+ public static async Task Launch ( string path1 , string path2 , string ? exePath = null )
6060 {
6161 var exe = FindExecutable ( exePath ) ;
6262 if ( exe == null )
@@ -77,10 +77,10 @@ Spreadsheet Compare (SPREADSHEETCOMPARE.EXE) was not found.
7777
7878 try
7979 {
80- using var process = LaunchProcess ( exe , tempFile ) ;
80+ using var process = await LaunchProcess ( exe , tempFile ) ;
8181
8282 JobObject . AssignProcess ( job , process . Handle ) ;
83- process . WaitForExit ( ) ;
83+ await process . WaitForExitAsync ( ) ;
8484 }
8585 catch when ( TempFiles . TryDelete ( tempFile ) )
8686 {
@@ -93,7 +93,7 @@ Spreadsheet Compare (SPREADSHEETCOMPARE.EXE) was not found.
9393 }
9494 }
9595
96- static Process LaunchProcess ( string exe , string tempFile )
96+ static async Task < Process > LaunchProcess ( string exe , string tempFile )
9797 {
9898 // Click-to-Run Office installs require launching via AppVLP.exe (the App-V
9999 // virtualization layer). SPREADSHEETCOMPARE.EXE crashes if launched directly.
@@ -104,7 +104,7 @@ static Process LaunchProcess(string exe, string tempFile)
104104 return LaunchDirect ( exe , tempFile ) ;
105105 }
106106
107- return LaunchViaAppVlp ( appVlp , exe , tempFile ) ;
107+ return await LaunchViaAppVlp ( appVlp , exe , tempFile ) ;
108108 }
109109
110110 static Process LaunchDirect ( string exe , string tempFile ) =>
@@ -115,16 +115,18 @@ static Process LaunchDirect(string exe, string tempFile) =>
115115 } )
116116 ?? throw new ( "Failed to start Spreadsheet Compare process" ) ;
117117
118- static Process LaunchViaAppVlp ( string appVlp , string exe , string tempFile )
118+ static readonly string lockFilePath = Path . Combine ( TempFiles . TempDirectory , ".lock" ) ;
119+
120+ static async Task < Process > LaunchViaAppVlp ( string appVlp , string exe , string tempFile )
119121 {
120122 // Serialize the snapshot-launch-identify sequence across concurrent
121123 // diffexcel instances. Without this, concurrent instances snapshot the
122124 // same PID set, race to claim the same SPREADSHEETCOMPARE process, and
123125 // leave others orphaned (not in any job object, so they survive when
124126 // diffexcel is killed).
125- using var mutex = new Mutex ( false , @"Global\MsExcelDiff_Launch" ) ;
126- mutex . WaitOne ( ) ;
127- try
127+ // Uses a file lock instead of a Mutex because file locks are not
128+ // thread-affine, allowing async code within the critical section.
129+ using ( await AcquireFileLock ( ) )
128130 {
129131 var existingPids = GetSpreadsheetComparePids ( ) ;
130132
@@ -137,15 +139,28 @@ static Process LaunchViaAppVlp(string appVlp, string exe, string tempFile)
137139
138140 // AppVLP.exe is a launcher that exits after starting the real process.
139141 // Find the actual SPREADSHEETCOMPARE process and wait on it.
140- launcher . WaitForExit ( ) ;
142+ await launcher . WaitForExitAsync ( ) ;
141143
142- return WaitForProcess ( existingPids )
144+ return await WaitForProcess ( existingPids )
143145 ?? throw new ( "Spreadsheet Compare did not start. Ensure the application is installed correctly." ) ;
144146 }
145- finally
147+ }
148+
149+ static async Task < FileStream > AcquireFileLock ( )
150+ {
151+ for ( var i = 0 ; i < 300 ; i ++ )
146152 {
147- mutex . ReleaseMutex ( ) ;
153+ try
154+ {
155+ return new ( lockFilePath , FileMode . OpenOrCreate , FileAccess . ReadWrite , FileShare . None ) ;
156+ }
157+ catch ( IOException )
158+ {
159+ await Task . Delay ( 100 ) ;
160+ }
148161 }
162+
163+ throw new IOException ( $ "Failed to acquire lock file: { lockFilePath } ") ;
149164 }
150165
151166 static HashSet < int > GetSpreadsheetComparePids ( ) =>
@@ -163,10 +178,10 @@ internal static HashSet<int> GetProcessPids(string processName)
163178 return pids ;
164179 }
165180
166- static Process ? WaitForProcess ( HashSet < int > existingPids ) =>
181+ static Task < Process ? > WaitForProcess ( HashSet < int > existingPids ) =>
167182 WaitForProcess ( "SPREADSHEETCOMPARE" , existingPids ) ;
168183
169- internal static Process ? WaitForProcess ( string processName , HashSet < int > existingPids , int maxAttempts = 100 )
184+ internal static async Task < Process ? > WaitForProcess ( string processName , HashSet < int > existingPids , int maxAttempts = 100 )
170185 {
171186 for ( var i = 0 ; i < maxAttempts ; i ++ )
172187 {
@@ -189,7 +204,7 @@ internal static HashSet<int> GetProcessPids(string processName)
189204 return result ;
190205 }
191206
192- Thread . Sleep ( 100 ) ;
207+ await Task . Delay ( 100 ) ;
193208 }
194209
195210 return null ;
0 commit comments