0

I am calling a VB 6.0 dll in Parallel.ForEach and expecting all calls to be started simultaneously or at least 2 of them based on my PC's cores or threads availability in thread pool

VB6 dll

Public Function DoJunk(ByVal counter As Long, ByVal data As String) As Integer
    Dim i As Long
    Dim j As Long
    Dim s As String

    Dim fno As Integer
    fno = FreeFile
    Open "E:\JunkVB6Dll\" & data & ".txt" For Output Access Write As #fno
    Print #fno, "Starting loop with counter = " & counter
    For i = 0 To counter
        Print #fno, "counting " & i
    Next

    Close #fno
    DoJunk = 1
End Function

counter is being passed from the caller to control execution time of the call and file is being written to make it an IO based process.

C# caller

private void ReportProgress(int value)
{
    progressBar.Value = value;
    //progressBar.Value++;
}

private void button1_Click(object sender, EventArgs e)
{
    progressBar.Value = 0;
    counter = 0;
    Stopwatch watch = new Stopwatch();
    watch.Start();

    //var range = Enumerable.Range(0, 100);
    var range = Enumerable.Range(0, 20);

    bool finished = false;


    Task.Factory.StartNew(() =>
    {
        Parallel.ForEach(range, i =>
        {
            #region COM CALL
            JunkProject.JunkClass junk = new JunkProject.JunkClass();
            try
            {
                Random rnd = new Random();
                int dice = rnd.Next(10, 40);

                int val = 0;
                if (i == 2)
                    val = junk.DoJunk(9000000, i.ToString());
                else
                    val = junk.DoJunk(dice * 10000, i.ToString());
                System.Diagnostics.Debug.Print(junk.GetHashCode().ToString());

                if (val == 1)
                {
                    Interlocked.Increment(ref counter);
                    progressBar.Invoke((Action)delegate { ReportProgress(counter); });
                }
                junk = null;
            }
            catch (Exception excep)
            {
                i = i;
            }
            finally { junk = null; }
            #endregion
        });
    }).ContinueWith(t =>
    {
        watch.Stop();
        MessageBox.Show(watch.ElapsedMilliseconds.ToString());
    });
}

This line is making a specific call longer than the others.

val = junk.DoJunk(9000000, i.ToString());

Here this second process is causing all calls inside the Parallel.ForEach to stop i.e. no other file is created unless this 2nd call gets completed.

Is it an expected behavior or i am doing something wrong?

bjan
  • 2,000
  • 7
  • 32
  • 64
  • 1
    https://stackoverflow.com/questions/26670370/c-sharp-com-objects-and-multithreading – CodeCaster Dec 10 '18 at 09:51
  • @CodeCaster What i have got from the shared link is that VB6 uses COM's Threading Model and COM uses caller's Threading Model. If my program runs in MTA only then these parallel calls will be parallel. Is it correct? – bjan Dec 10 '18 at 10:55
  • 1
    COM is complicated. Your COM component might be single-threaded, apartment model, or free-threaded. An apartment might be single or multi-threaded. If the COM component was written in VB6 it's likely that it is not thread safe at all and will run in a single-threaded apartment. You could try to [hack it](https://stackoverflow.com/questions/278389/is-possible-having-two-com-sta-instances-of-the-same-component), and it might seem to work, but you will have occasional problems still. The safest bet is to instantiate the COM component from two different App Domains so they have isolation. – John Wu Dec 10 '18 at 18:00

1 Answers1

0

As @John Wu suggested that you can create AppDomain to allow COM to run on different App Domain, I believe you could run your parallel like this.

        Parallel.ForEach(range, i =>
        {
            AppDomain otherDomain = AppDomain.CreateDomain(i.ToString());
            otherDomain.DoCallBack(delegate
            {
                //Your COM call
            });
        });

EDIT

Right.. I am not sure how can you set serializable on VB6.0 class. You can try the other way (Marshaling objects by reference). Noted: I haven't actually tested this, but I would like to know if that will work.

        Parallel.ForEach(range, i =>
        {
            AppDomain otherDomain = AppDomain.CreateDomain(i.ToString());
            var comCall = (ComCall) otherDomain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(ComCall).ToString());
            comCall.Run();
            AppDomain.Unload(otherDomain);
        });

and the class

public class ComCall : MarshalByRefObject
{
    public void Run()
    {
        //Your COM Call
    }
}

Here is also additional reference regarding the topic.

https://www.codeproject.com/Articles/14791/NET-Remoting-with-an-easy-example

OKEEngine
  • 888
  • 11
  • 28
  • This is failing on calling the delegate for COM call. "Type 'WindowsFormsApplication1.Form1+<>c__DisplayClass14' in assembly 'WindowsFormsApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.". – bjan Dec 13 '18 at 04:15